home *** CD-ROM | disk | FTP | other *** search
/ TeX 1995 July / TeX CD-ROM July 1995 (Disc 1)(Walnut Creek)(1995).ISO / macros / texinfo / C / info.c < prev    next >
Text File  |  1992-09-23  |  109KB  |  4,749 lines

  1. /* info -- a stand-alone Info program.
  2.  
  3.    Copyright (C) 1987, 1991 Free Software Foundation, Inc.
  4.  
  5.    This file is part of GNU Info.
  6.  
  7.    GNU Info is distributed in the hope that it will be useful,
  8.    but WITHOUT ANY WARRANTY.  No author or distributor accepts
  9.    responsibility to anyone for the consequences of using it or for
  10.    whether it serves any particular purpose or works at all, unless he
  11.    says so in writing.  Refer to the GNU Emacs General Public License
  12.    for full details.
  13.  
  14.    Everyone is granted permission to copy, modify and redistribute
  15.    GNU Info, but only under the conditions described in the GNU Emacs
  16.    General Public License.   A copy of this license is supposed to
  17.    have been given to you along with GNU Emacs so you can know your
  18.    rights and responsibilities.  It should be in a file named COPYING.
  19.    Among other things, the copyright notice and this notice must be
  20.    preserved on all copies.  */
  21.  
  22. /* This is GNU Info:
  23.  
  24.    Version 1.45  (Change "major_version" and "minor_version" below.)
  25.    Fri Feb  7 1992
  26. */
  27.  
  28. #include <stdio.h>
  29. #include <sys/types.h>
  30. #include <sys/stat.h>
  31. #include <signal.h>
  32. #include <pwd.h>
  33. #include <errno.h>
  34.  
  35. #if !defined (errno)
  36. extern int errno;
  37. #endif /* !errno */
  38.  
  39. #include <ctype.h>
  40. #include "getopt.h"
  41.  
  42. #if defined (hpux)
  43. #  define USG
  44. #endif /* hpux */
  45.  
  46. #if defined (USG)
  47.    struct passwd *getpwnam ();
  48. #  include <fcntl.h>
  49. #  include <termio.h>
  50. #  include <string.h>
  51.  
  52. #  if defined (USGr3)
  53. #    if !defined (M_XENIX)
  54. #      include <sys/stream.h>
  55. #      include <sys/ptem.h>
  56. #      undef TIOCGETC
  57. #    else /* M_XENIX */
  58. #      define tchars tc
  59. #      include <sys/ttold.h>
  60. #    endif /* M_XENIX */
  61. #  endif /* USGr3 */
  62. #  define bcopy(source, dest, count) memcpy(dest, source, count)
  63.    char *index(s,c) char *s; { char *strchr(); return strchr(s,c); }
  64.    char *rindex(s,c) char *s; { char *strrchr(); return strrchr(s,c); }
  65. #else /* !USG */
  66. #  include <sys/file.h>
  67. #  include <sgtty.h>
  68. #  include <strings.h>
  69. #endif /* USG */
  70.  
  71. #if !defined (DEFAULT_INFOPATH)
  72. #  define DEFAULT_INFOPATH \
  73.     ".:/usr/gnu/info:/usr/local/emacs/info:/usr/local/lib/emacs/info"
  74. #endif /* !DEFAULT_INFOPATH */
  75.  
  76. typedef struct nodeinfo {
  77.   char *filename;
  78.   char *nodename;
  79.   int pagetop;
  80.   int nodetop;
  81.   struct nodeinfo *next;
  82. } NODEINFO;
  83.  
  84. typedef struct indirectinfo {
  85.   char *filename;
  86.   int first_byte;
  87. } INDIRECT_INFO;
  88.  
  89. typedef int Function ();
  90. #define VOID_SIGHANDLER
  91. #if defined (VOID_SIGHANDLER)
  92. #  define SigHandler void
  93. #else
  94. #  define SigHandler int
  95. #endif /* !VOID_SIGHANDLER */
  96. #define barf(msg) fprintf(stderr, "%s\n", msg)
  97.  
  98. /* Some character stuff. */
  99. #define control_character_threshold 0x020 /* smaller than this is control */
  100. #define meta_character_threshold 0x07f    /* larger than this is Meta. */
  101. #define control_character_bit 0x40    /* 0x000000, must be off. */
  102. #define meta_character_bit 0x080    /* x0000000, must be on. */
  103.  
  104. #define info_separator_char '\037'
  105. #define start_of_node_string "\037"
  106.  
  107. #ifdef CTRL
  108. #undef CTRL
  109. #endif
  110.  
  111. #define CTRL(c) ((c) & (~control_character_bit))
  112. #define META(c) ((c) | meta_character_bit)
  113.  
  114. #define UNMETA(c) ((c) & (~meta_character_bit))
  115. #define UNCTRL(c) to_upper(((c)|control_character_bit))
  116.  
  117. #ifndef to_upper
  118. #define to_upper(c) (((c) < 'a' || (c) > 'z') ? (c) : (c) - 32)
  119. #define to_lower(c) (((c) < 'A' || (c) > 'Z') ? (c) : (c) + 32)
  120. #endif
  121.  
  122. #define CTRL_P(c) ((unsigned char) (c) < control_character_threshold)
  123. #define META_P(c) ((unsigned char) (c) > meta_character_threshold)
  124.  
  125. #define NEWLINE '\n'
  126. #define RETURN CTRL('M')
  127. #define DELETE 0x07f
  128. #define TAB '\t'
  129. #define ABORT_CHAR CTRL('G')
  130. #define PAGE CTRL('L')
  131. #define SPACE 0x020
  132. #define ESC CTRL('[')
  133. #define control_display_prefix '^'
  134.  
  135. #define TAG_TABLE_END_STRING "\037\nEND TAG TABLE"
  136. #define TAG_TABLE_BEG_STRING "\nTAG TABLE:\n"
  137. #define NODE_ID "Node:"
  138. #define NNODENAME 4        /* Default amount to grow nodename list by. */
  139. #define FILENAME_LEN 256
  140. #define NODENAME_LEN 256
  141. #define STRING_SIZE 256
  142. #define nodeend_sequence "\n\037"
  143.  
  144. #define whitespace(c) ((c) == ' ' || (c) == '\t')
  145. #define cr_whitespace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
  146.  
  147. /* All right, some windows stuff. */
  148.  
  149. typedef struct {
  150.   /* Absolute x and y coordinates for usable portion of this window. */
  151.   int left, top, right, bottom;
  152.   /* Absolute cursor position in this window. */
  153.   int ch, cv;
  154. } WINDOW;
  155.  
  156. typedef struct _wind_list {
  157.   int left, top, right, bottom;
  158.   int ch, cv;
  159.   struct _wind_list *next_window;
  160. } WINDOW_LIST;
  161.  
  162. WINDOW the_window = {0, 0, 80, 24, 0, 0};
  163. WINDOW_LIST *window_stack = (WINDOW_LIST *)NULL;
  164. WINDOW terminal_window = {0, 0, 80, 24, 0, 0};
  165.  
  166. /* Not really extern, but defined later in this file. */
  167. extern WINDOW echo_area;
  168. void *xmalloc (), *xrealloc ();
  169. SigHandler info_signal_handler ();
  170. char *getenv (), *next_info_file (), *opsys_filename ();
  171. int build_menu (), find_menu_node ();
  172. void swap_filestack (), pop_filestack ();
  173.  
  174. /* A crock, this should be done in a different way. */
  175. #define MAX_INDIRECT_FILES 100 
  176.  
  177. /* The info history list. */
  178. NODEINFO *Info_History = NULL;
  179.  
  180. /* ?Can't have more than xx files in the indirect list? */
  181. INDIRECT_INFO indirect_list[MAX_INDIRECT_FILES];
  182.  
  183. /* The filename of the currently loaded info file. */
  184. char current_info_file[FILENAME_LEN];
  185.  
  186. /* The nodename of the node the user is looking at. */
  187. char current_info_node[NODENAME_LEN];
  188.  
  189. /* The last file actually loaded.  Not the same as current info file. */
  190. char last_loaded_info_file[FILENAME_LEN];
  191.  
  192. /* Offsets in info_file of top and bottom of current_info_node. */
  193. int nodetop, nodebot;
  194.  
  195. /* Number of lines in this node. */
  196. int nodelines;
  197.  
  198. /* Buffer for the info file. */
  199. char *info_file = NULL;
  200.  
  201. /* Length of the above buffer. */
  202. int info_buffer_len;
  203.  
  204. /* Pointer to the start of a tag table, or NULL to show none. */
  205. char *tag_table = NULL;
  206.  
  207. /* Length of the above buffer. */
  208. int tag_buffer_len;        
  209.  
  210. /* Non-zero means that the tag table is indirect. */
  211. int indirect = 0;
  212. int indirect_top;
  213.  
  214. /* Offset in the buffer of the current pagetop. */
  215. int pagetop;
  216.  
  217. /* Offset in the buffer of the last displayed character. */
  218. int pagebot = 0;
  219.  
  220. /* If non-NULL, this is a colon separated list of directories to search
  221.    for a specific info file.  The user places this variable into his or
  222.    her environment. */
  223. char *infopath = NULL;
  224.  
  225. /* If filled, the name of a file to write to. */
  226. char dumpfile[FILENAME_LEN] = "";
  227.  
  228. /* This is the command to print a node. A default value is compiled in,
  229.    or it can be found from the environment as $INFO_PRINT_COMMAND. */
  230. char *print_command;
  231.  
  232. /* Non-zero means forst redisplay before prompt for the next command. */
  233. int window_bashed = 0;
  234.  
  235. /* **************************************************************** */
  236. /*                                    */
  237. /*            Getting Started.                */
  238. /*                                    */
  239. /* **************************************************************** */
  240.  
  241. /* Begin the Info session. */
  242.  
  243. /* Global is on until we are out of trouble. */
  244. int totally_inhibit_errors = 1;
  245.  
  246. /* Non-zero means print version info only. */
  247. int version_flag = 0;
  248.  
  249. /* The major and minor versions of Info. */
  250. int major_version = 1;
  251. int minor_version = 45;
  252.  
  253. struct option long_options[] = {
  254.   { "directory", 1, 0, 'd' },
  255.   { "node", 1, 0, 'n' },
  256.   { "file", 1, 0, 'f' },
  257.   { "output", 1, 0, 'o' },
  258.   { "version", 0, &version_flag, 1 },
  259.   {NULL, 0, NULL, 0}
  260. };
  261.  
  262. #define savestring(x) (char *) strcpy ((char *) xmalloc (1 + strlen (x)), (x))
  263.  
  264. main (argc, argv)
  265.      int argc;
  266.      char **argv;
  267. {
  268.   int c, ind, no_such_node = 0;
  269.   char filename[FILENAME_LEN];
  270.   char *nodename;
  271.   char **nodenames;
  272.   int nodenames_size, nodenames_index;
  273.   char *ptr, *env_infopath, *env_print_command;
  274.  
  275.   nodenames_index = 0;
  276.   nodenames = (char **) xmalloc ((nodenames_size = 1) * sizeof (char *));
  277.   nodenames[0] = (char *)NULL;
  278.  
  279.   env_infopath = getenv ("INFOPATH");
  280.   env_print_command = getenv ("INFO_PRINT_COMMAND");
  281.  
  282.   filename[0] = '\0';
  283.  
  284.   if (env_infopath && *env_infopath)
  285.     infopath = savestring (env_infopath);
  286.   else
  287.     infopath = savestring (DEFAULT_INFOPATH);
  288.  
  289.   if (env_print_command && *env_print_command)
  290.     print_command = savestring (env_print_command);
  291.   else
  292.     print_command = savestring (INFO_PRINT_COMMAND);
  293.  
  294.   while ((c = getopt_long (argc, argv, "d:n:f:o:", long_options, &ind)) != EOF)
  295.     {
  296.       if (c == 0 && long_options[ind].flag == 0)
  297.     c = long_options[ind].val;
  298.       switch (c)
  299.     {
  300.     case 0:
  301.       break;
  302.       
  303.     case 'd':
  304.       free (infopath);
  305.       infopath = savestring (optarg);
  306.       break;
  307.       
  308.     case 'n':
  309.  
  310.       if (nodenames_index + 2 > nodenames_size)
  311.         nodenames = (char **)
  312.           xrealloc (nodenames, (nodenames_size += 10) * sizeof (char *));
  313.  
  314.       nodenames[nodenames_index++] = optarg;
  315.       nodenames[nodenames_index] = (char *)NULL;
  316.       break;
  317.       
  318.     case 'f':
  319.       strncpy (filename, optarg, FILENAME_LEN);
  320.       break;
  321.       
  322.     case 'o':
  323.       strncpy (dumpfile, optarg, FILENAME_LEN);
  324.       break;
  325.       
  326.     default:
  327.       usage ();
  328.     }
  329.     }
  330.  
  331.   /* If the user specified `--version' then simply show the version
  332.      info at this time and exit. */
  333.   if (version_flag)
  334.     {
  335.       show_version_info (stdout);
  336.       exit (0);
  337.     }
  338.  
  339.   /* Okay, flags are parsed.  Get possible Info menuname. */
  340.  
  341.   if (*filename && (ptr = rindex (filename,'/')) != NULL )
  342.     {
  343.       /* Add filename's directory to path. */
  344.       char *temp;
  345.  
  346.       temp = (char *) xmalloc (2 + strlen (filename) + strlen (infopath));
  347.       strncpy (temp, filename, ptr - filename);
  348.       sprintf (temp + (ptr - filename), ":%s", infopath);
  349.       free (infopath);
  350.       infopath = temp;
  351.     }
  352.  
  353.   /* Start with DIR or whatever was specified. */
  354.   if (!get_node (filename, (nodenames[0] == NULL) ? "Top" : nodenames[0], 0))
  355.     {
  356.       if (filename[0])
  357.     {
  358.       fprintf (stderr, "Try just plain `info'.\n");
  359.       exit (1);
  360.     }
  361.       else
  362.     {
  363.       strcpy (filename, "DIR");
  364.  
  365.       if (!get_node ((char *)NULL, (char *)NULL, 1))
  366.         {
  367.           fprintf (stderr,
  368.                "%s: Cannot find \"%s\", anywhere along the search ",
  369.                argv[0], filename);
  370.           fprintf (stderr, "path of\n\"%s\".\n", infopath);
  371.           exit (1);
  372.         }
  373.     }
  374.     }
  375.  
  376.   totally_inhibit_errors = 0;
  377.  
  378.   for (ind = 1 ; ind < nodenames_index ; ind++)
  379.     get_node (filename, nodenames[ind], 0);
  380.  
  381.   nodename = nodenames[nodenames_index > 0 ? nodenames_index - 1 : 0];
  382.   if (!nodename)
  383.     {
  384.       nodename = (char *) xmalloc (NODENAME_LEN);
  385.       *nodename = '\0';
  386.     }
  387.   
  388.   if (optind != argc)
  389.     {
  390.       while (optind != argc)
  391.     {
  392.       if (!build_menu ())
  393.           {
  394.           display_error ("There is no menu in node \"%s\"",
  395.                  current_info_node );
  396.           no_such_node++;
  397.           break;
  398.         }
  399.       else if (!find_menu_node (argv[optind], nodename))
  400.         {
  401.           display_error
  402.         ("There is no menu entry for \"%s\" in node \"%s\"",
  403.          argv[optind], nodename );
  404.           no_such_node++;
  405.           break;
  406.         }
  407.       else if (!get_node ((char *)NULL, nodename, 0))
  408.         {
  409.           no_such_node++;
  410.           break;
  411.         }
  412.       else
  413.          {
  414. #if 1
  415.           /* RMS says not to type this stuff out because he expects
  416.          programs to call Info instead of interactive users. */
  417.           printf ("%s.. ",argv[optind]);
  418.           fflush (stdout);
  419. #endif
  420.           optind++;
  421.         }
  422.     }
  423.     }
  424.  
  425.   /* If we are outputting to a file, and the node was not found, exit. */
  426.   if (no_such_node && dumpfile[0])
  427.     exit (1);
  428.   else
  429.     begin_info_session ();
  430.  
  431.   exit (0);
  432. }
  433.  
  434. usage ()
  435. {
  436.   fprintf (stderr,"%s\n%s\n%s\n%s\n",
  437. "Usage: info [-d dir-path] [-f info-file] [-n node-name -n node-name ...]",
  438. "            [-o output-file] [--directory dir-path] [--file info-file]",
  439. "            [--node node-name --node node-name ...] [--version]",
  440. "            [--output output-file] [menu-selection...]");
  441.   exit (1);
  442. }
  443.  
  444. /* Print the version of info to standard output. */
  445. show_version_info (stream)
  446.      FILE *stream;
  447. {
  448.   fprintf (stream, "GNU Info version %d.%d\n", major_version, minor_version);
  449.   fflush (stream);
  450. }
  451.  
  452. #if defined (SIGTSTP)
  453. Function *old_tstp;
  454. Function *old_ttou, *old_ttin;
  455. #endif /* SIGTSTP */
  456.  
  457. #if defined (SIGWINCH)
  458. Function *old_winch;
  459. #endif /* SIGWINCH */
  460.  
  461. /* Start using Info. */
  462. begin_info_session ()
  463. {
  464.   /* If the user just wants to dump the node, then do that. */
  465.   if (dumpfile[0])
  466.     {
  467.       dump_current_node (dumpfile);
  468.       exit (0);
  469.     }
  470.  
  471.   init_terminal_io ();
  472.  
  473.   /* Install handlers for restoring/breaking the screen. */
  474.  
  475.   install_signals ();
  476.   new_echo_area ();
  477.  
  478.   print_string ("Welcome to Info!  Type \"?\" for help. ");
  479.   close_echo_area ();
  480.   toploop ();
  481.   goto_xy (the_window.left, the_window.bottom + 1);
  482.   restore_io ();
  483. }
  484.  
  485. /* What to do before processing a stop signal. */
  486. before_stop_signal ()
  487. {
  488.   restore_io ();
  489. }
  490.  
  491. /* What to do after processing a stop signal. */
  492. after_stop_signal ()
  493. {
  494.   clear_screen ();
  495.   display_page ();
  496.   goto_xy (the_window.ch, the_window.cv);
  497.   opsys_init_terminal ();
  498. }
  499.  
  500. /* Do the right thing with this signal. */
  501. SigHandler
  502. info_signal_handler (sig)
  503.      int sig;
  504. {
  505.   switch (sig)
  506.     {
  507. #if defined (SIGTSTP)
  508.     case SIGTSTP:
  509.     case SIGTTOU:
  510.     case SIGTTIN:
  511.       before_stop_signal ();
  512.       signal (sig, SIG_DFL);
  513. #if !defined (USG)
  514.       sigsetmask (sigblock (0) & ~sigmask (sig));
  515. #endif /* !USG */
  516.       kill (getpid (), sig);
  517.       after_stop_signal ();
  518.       signal (sig, info_signal_handler);
  519.       break;
  520. #endif /* SIGTSTP */
  521.  
  522. #if defined (SIGWINCH)
  523.     case SIGWINCH:
  524.       /* Window has changed.  Get the size of the new window, and rebuild our
  525.          window chain. */
  526.       {
  527.     int display_page ();
  528.     extern char *widest_line;
  529.     extern WINDOW terminal_window;
  530.     extern WINDOW_LIST *window_stack;
  531.     extern int terminal_rows, terminal_columns;
  532.     int delta_width, delta_height, right, bottom;
  533.  
  534.     right = get_terminal_columns ();
  535.     bottom = get_terminal_rows ();
  536.  
  537.     delta_width = right - terminal_columns;
  538.     delta_height = bottom - terminal_rows;
  539.  
  540.     terminal_columns = right;
  541.     terminal_rows = bottom;
  542.  
  543.     /* Save current window, whatever it is. */
  544.     push_window ();
  545.  
  546.     /* Change the value of the widest_line. */
  547.     free (widest_line);
  548.     widest_line = (char *) xmalloc (right);
  549.  
  550.     /* Make the new window.  Map over all windows in window list. */
  551.     {
  552.       WINDOW_LIST *wind = window_stack;
  553.       extern WINDOW modeline_window;
  554.  
  555.       while (wind != (WINDOW_LIST *)NULL)
  556.         {
  557.           adjust_wind ((WINDOW *)wind, delta_width, delta_height);
  558.           wind = wind->next_window;
  559.         }
  560.  
  561.       /* Adjust the other windows that we know about. */
  562.       adjust_wind (&terminal_window, delta_width, delta_height);
  563.       adjust_wind (&echo_area, delta_width, delta_height);
  564.       adjust_wind (&modeline_window, delta_width, delta_height);
  565.     }
  566.  
  567.     /* Clear and redisplay the entire terminal window. */
  568.     set_window (&terminal_window);
  569.     clear_screen ();
  570.  
  571.     /* Redisplay the contents of the screen. */
  572.     with_output_to_window (&terminal_window, display_page);
  573.  
  574.     /* Get back the current window. */
  575.     pop_window ();
  576.       }
  577.       break;
  578. #endif /* SIGWINCH */
  579.  
  580.     case SIGINT:
  581.       restore_io ();
  582.       exit (1);
  583.       break;
  584.     }
  585. }
  586.  
  587. install_signals ()
  588. {
  589. #if defined (SIGTSTP)
  590.   old_tstp = (Function *) signal (SIGTSTP, info_signal_handler);
  591.   old_ttou = (Function *) signal (SIGTTOU, info_signal_handler);
  592.   old_ttin = (Function *) signal (SIGTTIN, info_signal_handler);
  593. #endif /* SIGTSTP */
  594.  
  595. #if defined (SIGWINCH)
  596.   old_winch = (Function *) signal (SIGWINCH, info_signal_handler);
  597. #endif /* SIGWINCH */
  598.  
  599.   signal (SIGINT, info_signal_handler);
  600. }
  601.  
  602. adjust_wind (wind, delta_width, delta_height)
  603.      WINDOW *wind;
  604.      int delta_width, delta_height;
  605. {
  606.   wind->right += delta_width;
  607.   wind->bottom += delta_height;
  608.   wind->ch += delta_width;
  609.   wind->cv += delta_height;
  610.  
  611.   /* Ugly hack to fix busted windows code.  If the window we are adjusting
  612.      already has a TOP offset, then adjust that also. */
  613.   if (wind->top)
  614.     wind->top += delta_height;
  615. }
  616.  
  617. /* **************************************************************** */
  618. /*                                    */
  619. /*            Completing Things                */
  620. /*                                    */
  621. /* **************************************************************** */
  622.  
  623. typedef struct completion_entry
  624. {
  625.   char *identifier;
  626.   char *data;
  627.   struct completion_entry *next;
  628. }                COMP_ENTRY;
  629.  
  630. /* The linked list of COMP_ENTRY structures that you create. */
  631. COMP_ENTRY *completion_list = (COMP_ENTRY *) NULL;
  632.  
  633. /* The vector of COMP_ENTRY pointers that COMPLETE returns. */
  634. COMP_ENTRY **completions = NULL;
  635.  
  636. /* The number of elements in the above vector. */
  637. int completion_count;
  638.  
  639. /* Initial size of COMPLETIONS array. */
  640. #define INITIAL_COMPLETIONS_CORE_SIZE 200
  641.  
  642. /* Current size of the completion array in core. */
  643. int completions_core_size = 0;
  644.  
  645. /* Ease the typing task.  Another name for the I'th
  646.    IDENTIFIER of COMPLETIONS. */
  647. #define completion_id(i) ((completions[(i)])->identifier)
  648.  
  649. /* The number of completions that can be present before the help
  650.    function starts asking you about whether it should print them
  651.    all or not. */
  652. int completion_query_threshold = 100;
  653.  
  654. free_completion_list ()
  655. {
  656.   COMP_ENTRY *temp;
  657.   while (completion_list)
  658.     {
  659.       temp = completion_list;
  660.  
  661.       if (completion_list->identifier)
  662.     free (completion_list->identifier);
  663.  
  664.       if (completion_list->data)
  665.     free (completion_list->data);
  666.  
  667.       completion_list = completion_list->next;
  668.       free (temp);
  669.     }
  670. }
  671.  
  672. /* Add a single completion to COMPLETION_LIST.
  673.    IDENTIFIER is the string that the user should type.
  674.    DATA should just be a pointer to some random data that you wish to
  675.    have associated with the identifier, but I'm too stupid for that, so
  676.    it must be a string as well.  This allocates the space for the strings
  677.    so you don't necessarily have to. */
  678. add_completion (identifier, data)
  679.      char *identifier, *data;
  680. {
  681.   COMP_ENTRY *temp = (COMP_ENTRY *) xmalloc (sizeof (COMP_ENTRY));
  682.  
  683.   temp->identifier = (char *) xmalloc (strlen (identifier) + 1);
  684.   strcpy (temp->identifier, identifier);
  685.  
  686.   temp->data = (char *) xmalloc (strlen (data) + 1);
  687.   strcpy (temp->data, data);
  688.  
  689.   temp->next = completion_list;
  690.   completion_list = temp;
  691. }
  692.  
  693. /* Function for reading a line.  Supports completion on COMPLETION_LIST
  694.    if you pass COMPLETING as non-zero.  Prompt is either a prompt or
  695.    NULL, LINE is the place to store the characters that are read.
  696.    LINE may start out already containing some characters; if so, they
  697.    are printed.  MAXCHARS tells how many characters can fit in the
  698.    buffer at LINE.  readline () returns zero if the user types the
  699.    abort character.  LINE is returned with a '\0' at the end, not a '\n'. */
  700. int
  701. readline (prompt, line, maxchars, completing)
  702.      char *prompt, *line;
  703.      int maxchars;
  704.      int completing;
  705. {
  706.   int character;
  707.   int readline_ch, readline_cv;
  708.   int current_len = strlen (line);
  709.   int just_completed = 0;        /* Have we just done a completion? */
  710.   int meta_flag = 0;
  711.  
  712.   new_echo_area ();
  713.  
  714.   if (prompt)
  715.     print_string ("%s", prompt);
  716.  
  717.   readline_ch = the_window.ch;
  718.   readline_cv = the_window.cv;
  719.  
  720.   print_string ("%s", line);
  721.  
  722.   while (1)
  723.     {
  724.       line[current_len] = '\0';
  725.       goto_xy (readline_ch, readline_cv);
  726.       print_string ("%s", line);
  727.       clear_eol ();
  728.  
  729.       if (just_completed)
  730.     just_completed--;
  731.  
  732.       character = blink_cursor ();
  733.       if (meta_flag)
  734.     {
  735.       character = META (character);
  736.       meta_flag = 0;
  737.     }
  738.  
  739.       if (META_P (character))
  740.     character = META (to_upper (UNMETA (character)));
  741.  
  742.       switch (character)
  743.     {
  744.     case EOF:
  745.       character = '\n';
  746.  
  747.     case ESC:
  748.       meta_flag++;
  749.       break;
  750.  
  751.     case META (DELETE):
  752.     case CTRL ('W'):
  753.       while (current_len && line[current_len] == SPACE)
  754.         current_len--;
  755.  
  756.       if (!current_len)
  757.         break;
  758.  
  759.       while (current_len && line[current_len] != SPACE)
  760.         current_len--;
  761.  
  762.       break;
  763.  
  764.     case CTRL ('U'):
  765.       current_len = 0;
  766.       break;
  767.  
  768.     case '\b':
  769.     case 0x07f:
  770.       if (current_len)
  771.         current_len--;
  772.       else
  773.         ding ();
  774.       break;
  775.  
  776.     case '\n':
  777.     case '\r':
  778.       if (completing)
  779.         {
  780.           extern int completion_count;
  781.  
  782.           try_complete (line);
  783.  
  784.           if (completion_count >= 1)
  785.         {
  786.           close_echo_area ();
  787.           return (1);
  788.         }
  789.           else
  790.         {
  791.           current_len = strlen (line);
  792.           break;
  793.         }
  794.         }
  795.       else
  796.         {
  797.           close_echo_area ();
  798.           return (1);
  799.         }
  800.  
  801.     case ABORT_CHAR:
  802.       ding ();
  803.  
  804.       if (current_len)
  805.         {
  806.           current_len = 0;
  807.         }
  808.       else
  809.         {
  810.           close_echo_area ();
  811.           clear_echo_area ();
  812.           return (0);
  813.         }
  814.       break;
  815.  
  816.     case ' ':
  817.     case '\t':
  818.     case '?':
  819.       if (completing)
  820.         {
  821.           extern int completion_count;
  822.  
  823.           if (character == '?' || just_completed)
  824.         {
  825.           help_possible_completions (line);
  826.           break;
  827.         }
  828.           else
  829.         {
  830.           char temp_line[NODENAME_LEN];
  831.           strcpy (temp_line, line);
  832.           try_complete (line); just_completed = 2;
  833.           if (completion_count != 1 && character == SPACE)
  834.             {
  835.               if (strcmp (temp_line, line) == 0)
  836.             {
  837.               line[current_len] = SPACE;
  838.               line[current_len + 1] = '\0';
  839.               strcpy (temp_line, line);
  840.               try_complete (line);
  841.               if (completion_count == 0)
  842.                 {
  843.                   line[current_len] = '\0';
  844.                   ding ();
  845.                 }
  846.             }
  847.             }
  848.           current_len = strlen (line);
  849.           if (completion_count == 0)
  850.             ding ();
  851.           break;
  852.         }
  853.         }
  854.       /* Do *NOT* put anything in-between the completing cases and
  855.          the default: case.  No.  Because the SPC, TAB and `?' get
  856.          treated as normal characters by falling through the
  857.          "if (completing)" test above. */
  858.     default:
  859.       if (!CTRL_P (character) &&
  860.           !META_P (character) &&
  861.           current_len < maxchars)
  862.         line[current_len++] = character;
  863.       else
  864.         ding ();
  865.     }
  866.     }
  867. }
  868.  
  869. /* Initialize whatever the completer is using. */
  870. init_completer ()
  871. {
  872.   if (completions_core_size != INITIAL_COMPLETIONS_CORE_SIZE)
  873.     {
  874.       if (completions)
  875.     free (completions);
  876.  
  877.       completions = (COMP_ENTRY **)
  878.     xmalloc ((sizeof (COMP_ENTRY *))
  879.          * (completions_core_size = INITIAL_COMPLETIONS_CORE_SIZE));
  880.     }
  881.   completion_count = 0;
  882. }
  883.  
  884. /* Reverse the completion list passed in LIST, and
  885.    return a pointer to the new head. */
  886. COMP_ENTRY *
  887. reverse_list (list)
  888.      COMP_ENTRY *list;
  889. {
  890.   COMP_ENTRY *next;
  891.   COMP_ENTRY *prev = (COMP_ENTRY *) NULL;
  892.  
  893.   while (list)
  894.     {
  895.       next = list->next;
  896.       list->next = prev;
  897.       prev = list;
  898.       list = next;
  899.     }
  900.   return (prev);
  901. }
  902.  
  903. /* Remember the possible completion passed in POINTER on the
  904.    completions list. */
  905. remember_completion (pointer)
  906.      COMP_ENTRY *pointer;
  907. {
  908.   if (completion_count == completions_core_size)
  909.     {
  910.       COMP_ENTRY **temp = (COMP_ENTRY **)
  911.     realloc (completions, ((sizeof (COMP_ENTRY *))
  912.                    * (completions_core_size +=
  913.                   INITIAL_COMPLETIONS_CORE_SIZE)));
  914.       if (!temp)
  915.     {
  916.       display_error ("Too many completions (~d)!  Out of core!",
  917.              completion_count);
  918.       return;
  919.     }
  920.       else
  921.     completions = temp;
  922.     }
  923.   completions[completion_count++] = pointer;
  924. }
  925.  
  926. /* Complete TEXT from identifiers in LIST.  Place the resultant
  927.    completions in COMPLETIONS, and the number of completions in
  928.    COMPLETION_COUNT. Modify TEXT to contain the least common
  929.    denominator of all the completions found. */
  930. int
  931. complete (text, list)
  932.      char *text;
  933.      COMP_ENTRY *list;
  934. {
  935.   int low_match, i, idx;
  936.   int string_length = strlen (text);
  937.  
  938.   init_completer ();
  939.   low_match = 100000;        /* Some large number. */
  940.  
  941.   while (list)
  942.     {
  943.       if (strnicmp (text, list->identifier, string_length) == 0)
  944.     remember_completion (list);
  945.       list = list->next;
  946.     }
  947.  
  948.   if (completion_count == 0)
  949.     return (0);
  950.  
  951.   if (completion_count == 1)
  952.     {                /* One completion */
  953.       strcpy (text, completion_id (0));
  954.       return (1);
  955.     }
  956.  
  957.   /* Else find the least common denominator */
  958.  
  959.   idx = 1;
  960.  
  961.   while (idx < completion_count)
  962.     {
  963.       int c1, c2;
  964.       for (i = 0;
  965.        (c1 = to_lower (completion_id (idx - 1)[i])) &&
  966.        (c2 = to_lower (completion_id (idx)[i]));
  967.        i++)
  968.     if (c1 != c2)
  969.       break;
  970.  
  971.       if (low_match > i)
  972.     low_match = i;
  973.       idx++;
  974.     }
  975.  
  976.   strncpy (text, completion_id (0), low_match);
  977.   text[low_match] = '\0';
  978.   return (1);
  979. }
  980.  
  981. /* Complete TEXT from the completion structures in COMPLETION_LIST. */
  982. int
  983. try_complete (text)
  984.      char *text;
  985. {
  986.   return (complete (text, completion_list));
  987. }
  988.  
  989. /* The function that prints out the possible completions. */
  990. help_possible_completions (text)
  991.      char *text;
  992. {
  993.   char temp_string[2000];
  994.  
  995.   goto_xy (the_window.left, the_window.top);
  996.   strcpy (temp_string, text);
  997.   try_complete (temp_string);
  998.  
  999.   open_typeout ();
  1000.  
  1001.   if (completion_count == 0)
  1002.     {
  1003.       print_string ("There are no possible completions.\n");
  1004.       goto print_done;
  1005.     }
  1006.  
  1007.   if (completion_count == 1)
  1008.     {
  1009.       print_string
  1010.     ("The only possible completion of what you have typed is:\n\n");
  1011.       print_string ("%s", completion_id(0));
  1012.       goto print_done;
  1013.     }
  1014.  
  1015.   if (completion_count >= completion_query_threshold)
  1016.     {
  1017.       print_string
  1018.     ("\nThere are %d completions.  Do you really want to see them all",
  1019.      completion_count);
  1020.  
  1021.       if (!get_y_or_n_p ())
  1022.     return;
  1023.     }
  1024.  
  1025.   print_string ("\nThe %d completions of what you have typed are:\n\n",
  1026.         completion_count);
  1027.  
  1028.   {
  1029.     int idx = 0;
  1030.     int counter = 0;
  1031.     int columns = (the_window.right - the_window.left) / 30;
  1032.  
  1033.     while (idx < completion_count)
  1034.       {
  1035.     if (counter == columns)
  1036.       {
  1037.         charout ('\n');
  1038.         counter = 0;
  1039.       }
  1040.     indent_to (counter * 30);
  1041.     print_string ("%s", completion_id (idx));
  1042.     counter++;
  1043.     idx++;
  1044.       }
  1045.   }
  1046.  
  1047. print_done:
  1048.   print_string ("\n\n-----------------\n");
  1049.   close_typeout ();
  1050. }
  1051.  
  1052. /* Return the next file that should be searched, or NULL if we are at the end
  1053.    of the info file. If the FILE argument is provided, begin the search there,
  1054.    if REWIND is non-zero start the search at the beginning of the list.
  1055.  
  1056.    The list is the one built by an indirect tag table, on the supposition
  1057.    that those files form a logical set to search if we are in one of them.
  1058.    If no such list is current (either it doesn't exist, or FILE isn't on
  1059.    it) the search list is set to be last_loaded_info_file */
  1060. char *
  1061. next_info_file (file, rewind)
  1062.      char *file;        /* file to set `next' to. May be NULL. */
  1063.      int rewind;        /* should I rewind the file list?  */
  1064. {
  1065.   static int index = -1;
  1066.  
  1067.   if (file != NULL)
  1068.     {
  1069.       char *ptr = rindex (file,'/');
  1070.  
  1071.       if (ptr != NULL)
  1072.     file = ptr + 1;
  1073.  
  1074.       for (index = 0;
  1075.        index < MAX_INDIRECT_FILES &&
  1076.        indirect_list[index].filename != (char *)NULL;
  1077.        index++)
  1078.     {
  1079.       if (strcmp (file, indirect_list[index].filename) == 0)
  1080.         return (file);
  1081.     }
  1082.  
  1083.       /* OK, we are not on the current indirect_list. This means that
  1084.      we have switched to another node that has no indirect list,
  1085.      so forget the old one. */
  1086.       for (index = 0;
  1087.        index < MAX_INDIRECT_FILES &&
  1088.        indirect_list[index].filename != (char *)NULL;
  1089.        index++)
  1090.     {
  1091.       free (indirect_list[index].filename);
  1092.       indirect_list[index].filename = (char *)NULL;
  1093.     }
  1094.       return (indirect_list[0].filename = savestring (file));
  1095.     }
  1096.   else if (rewind)
  1097.     {
  1098.       index = 0;
  1099.  
  1100.       if (indirect_list[0].filename == (char *)NULL)
  1101.     indirect_list[0].filename = savestring (last_loaded_info_file);
  1102.     }
  1103.   else
  1104.     index++;
  1105.   
  1106.   if (index < MAX_INDIRECT_FILES &&
  1107.       indirect_list[index].filename != (char *)NULL)
  1108.     return (indirect_list[index].filename);
  1109.  
  1110.   index = -1;
  1111.   return (NULL);
  1112. }
  1113.  
  1114. /* **************************************************************** */
  1115. /*                                    */
  1116. /*            Getting Nodes                    */
  1117. /*                                    */
  1118. /* **************************************************************** */
  1119.  
  1120. /* A node name looks like:
  1121.    Node: nodename with spaces but not a comma,
  1122. or Node: (filename-containing-node)node-within-file
  1123. or Node: (filename)
  1124.  
  1125.    The latter case implies a nodename of "Top".  All files are
  1126.    supposed to have one.
  1127.  
  1128.    Lastly, the nodename specified could be "*", which specifies the
  1129.    entire file. */
  1130.  
  1131. /* Return the directory portion of FILENAME, i.e., everything before the
  1132.    last slash. */
  1133. static char *
  1134. file_directory (filename)
  1135.      char *filename;
  1136. {
  1137.   register char *scan;
  1138.   register int length;
  1139.   char *result;
  1140.  
  1141.   scan = filename;
  1142.  
  1143.   while (*scan++ != '\0');
  1144.  
  1145.   while (1)
  1146.     {
  1147.       if (scan == filename)
  1148.     break;
  1149.  
  1150.       if ((*--scan) == '/')
  1151.     {
  1152.       scan++;
  1153.       break;
  1154.     }
  1155.     }
  1156.  
  1157.   length = scan - filename;
  1158.   result = (char *) xmalloc (length + 1);
  1159.   strncpy (result, filename, length);
  1160.   result[length] = '\0';
  1161.  
  1162.   return (result);
  1163. }
  1164.  
  1165. /* Given FILENAME and DIRECTORY return a newly allocated string which
  1166.    is either the two concatenated, or simply FILENAME if it is absolute
  1167.    already. */
  1168. static char *
  1169. file_absolutize (filename, directory)
  1170.      char *filename, *directory;
  1171. {
  1172.   register int filename_len, directory_len;
  1173.   char *result;
  1174.  
  1175.   if (filename[0] == '/')
  1176.     return (savestring (filename));
  1177.  
  1178.   filename_len = strlen (filename);
  1179.   directory_len = strlen (directory);
  1180.   result = (char *) xmalloc (directory_len + filename_len + 1);
  1181.  
  1182.   strcpy (result, directory);
  1183.   strcat (result, filename);
  1184.  
  1185.   return (result);
  1186. }
  1187.  
  1188. /* Load FILENAME.  If REMEMBER_NAME is non-zero, then remember the
  1189.    loaded filename in CURRENT_INFO_FILE.  In either case, remember
  1190.    the name of this file in LAST_LOADED_INFO_FILE. */
  1191. int
  1192. get_info_file (filename, remember_name)
  1193.      char *filename;
  1194.      int remember_name;
  1195. {
  1196.   FILE *input_stream;
  1197.   struct stat file_info;
  1198.   int pointer, result;
  1199.   char tempname[FILENAME_LEN];
  1200.  
  1201.   /* Get real filename. */
  1202.   strcpy (tempname, opsys_filename (filename));
  1203.  
  1204.   /* If the file doesn't exist, try again with the name in lower case. */
  1205.   result = stat (tempname, &file_info);
  1206.  
  1207.   if (result < 0)
  1208.     {
  1209.       register int i;
  1210.       char *lowered_name;
  1211.  
  1212.       lowered_name = (char *)xmalloc (1 + strlen (filename));
  1213.  
  1214.       for (i = 0; lowered_name[i] = to_lower (filename[i]); i++)
  1215.     ;
  1216.  
  1217.       strcpy (tempname, opsys_filename (lowered_name));
  1218.       result = stat (tempname, &file_info);
  1219.     }
  1220.  
  1221.   /* See if this file is the last loaded one. */
  1222.   if (!result && (strcmp (last_loaded_info_file, tempname) == 0))
  1223.     return (1);
  1224.  
  1225.   /* Now try to open the file. */
  1226.   if (result || (input_stream = fopen (tempname, "r")) == NULL)
  1227.     {
  1228.       file_error (tempname);
  1229.       return (0);
  1230.     }
  1231.  
  1232.   /* If we already have a file loaded, then free it first. */
  1233.   if (info_file)
  1234.     {
  1235.       free (info_file);
  1236.  
  1237.       if (!indirect)
  1238.     {
  1239.       /* Then the tag table is also no longer valid. */
  1240.       tag_table = (char *) NULL;
  1241.     }
  1242.     }
  1243.  
  1244.   /* Read the contents of the file into a new buffer. */
  1245.  
  1246.   info_file = (char *) xmalloc (info_buffer_len = file_info.st_size);
  1247.   fread (info_file, 1, info_buffer_len, input_stream);
  1248.   fclose (input_stream);
  1249.   strcpy (last_loaded_info_file, tempname);
  1250.   if (remember_name)
  1251.     {
  1252.       strcpy (current_info_file, tempname);
  1253.       if (indirect)
  1254.     {
  1255.       int idx;
  1256.       indirect = 0;
  1257.       free (tag_table);
  1258.     }
  1259.     }
  1260.   else
  1261.     return (1);
  1262.  
  1263.   /* Force redisplay, since we are looking at a new file. */
  1264.   window_bashed = 1;
  1265.  
  1266.   /* The file has been read, and we don't know anything about it.
  1267.      Find out if it contains a tag table. */
  1268.  
  1269.   tag_table = NULL;        /* assume none. */
  1270.   indirect = 0;
  1271.   tag_buffer_len = 0;
  1272.  
  1273.   set_search_constraints (info_file, info_buffer_len);
  1274.  
  1275.   /* Go to the last few lines in the file. */
  1276.   pointer = back_lines (8, info_buffer_len);
  1277.   pointer = search_forward (TAG_TABLE_END_STRING, pointer);
  1278.  
  1279.   if (pointer > -1)
  1280.     {
  1281.       /* Then there is a tag table.  Find the start of it,
  1282.      and remember that. */
  1283.       pointer = search_backward (TAG_TABLE_BEG_STRING, pointer);
  1284.  
  1285.       /* Handle error for malformed info file. */
  1286.       if (pointer < 0)
  1287.     display_error ("Start of tag table not found!");
  1288.       else
  1289.     {
  1290.       /* No problem.  If this file is an indirect file, then the contents
  1291.          of the tag table must remain in RAM the entire time.  Otherwise,
  1292.          we can flush the tag table with the file when the file is flushed.
  1293.          So, if indirect, remember that, and copy the table to another
  1294.          place.*/
  1295.  
  1296.       int indirect_check = forward_lines (2, pointer);
  1297.  
  1298.       tag_table = info_file + pointer;
  1299.       tag_buffer_len = info_buffer_len - pointer;
  1300.  
  1301.       /* Shorten the search constraints. */
  1302.       info_buffer_len = pointer;
  1303.  
  1304.       if (looking_at ("(Indirect)\n", indirect_check))
  1305.         {
  1306.           /* We have to find the start of the indirect file's
  1307.          information. */
  1308.           tag_table = (char *) xmalloc (tag_buffer_len);
  1309.  
  1310.           bcopy (&info_file[indirect_check], tag_table, tag_buffer_len);
  1311.  
  1312.           /* Find the list of filenames. */
  1313.           indirect_top = search_backward ("Indirect:\n", indirect_check);
  1314.           if (indirect_top < 0)
  1315.         {
  1316.           free (tag_table);
  1317.           tag_table = (char *) NULL;
  1318.           display_error ("Start of INDIRECT tag table not found!");
  1319.           return (0);
  1320.         }
  1321.  
  1322.           /* Remember the filenames, and their byte offsets. */
  1323.           {
  1324.         /* Index into the filename/offsets array. */
  1325.         int idx, temp_first_byte;
  1326.         char temp_filename[FILENAME_LEN];
  1327.         char *directory = file_directory (tempname);
  1328.  
  1329.         info_buffer_len = indirect_top;
  1330.  
  1331.         /* For each line, scan the info into globals.  Then save
  1332.                the information in the INDIRECT_INFO structure. */
  1333.  
  1334.         for (idx = 0; idx < MAX_INDIRECT_FILES &&
  1335.              indirect_list[idx].filename != (char *) NULL;
  1336.              idx++)
  1337.             {
  1338.              free (indirect_list[idx].filename);
  1339.              indirect_list[idx].filename = (char *) NULL;
  1340.           }
  1341.         
  1342.         for (idx = 0;info_file[indirect_top] != info_separator_char &&
  1343.              idx < MAX_INDIRECT_FILES;)
  1344.           {
  1345.             indirect_top = forward_lines (1, indirect_top);
  1346.             if (info_file[indirect_top] == info_separator_char)
  1347.               break;
  1348.  
  1349.             /* Ignore blank lines. */
  1350.             if (info_file[indirect_top] == '\n')
  1351.               continue;
  1352.  
  1353.             sscanf (&info_file[indirect_top], "%s%d",
  1354.                 temp_filename, &temp_first_byte);
  1355.  
  1356.             if (strlen (temp_filename))
  1357.               {
  1358.             temp_filename[strlen (temp_filename) - 1] = '\0';
  1359.             indirect_list[idx].filename =
  1360.               file_absolutize (temp_filename, directory);
  1361.             indirect_list[idx].first_byte = temp_first_byte;
  1362.             idx++;
  1363.               }
  1364.           }
  1365.  
  1366.         free (directory);
  1367.  
  1368.         /* Terminate the table. */
  1369.         if (idx == MAX_INDIRECT_FILES)
  1370.           {
  1371.             display_error
  1372.               ("Sorry, the INDIRECT file array isn't large enough.");
  1373.             idx--;
  1374.           }
  1375.         indirect_list[idx].filename = (char *) NULL;
  1376.           }
  1377.           indirect = 1;
  1378.        } else {
  1379.           ;
  1380.        }
  1381.     }
  1382.     }
  1383.   return (1);
  1384. }
  1385.  
  1386. /* Make current_info_node be NODENAME.  This could involve loading
  1387.    a file, etc.  POPPING is non-zero if we got here because we are
  1388.    popping one level. */
  1389. int
  1390. get_node (filename, nodename, popping)
  1391.      char *nodename, *filename;
  1392.      int popping;
  1393. {
  1394.   int pointer;
  1395.   char internal_filename[FILENAME_LEN];
  1396.   char internal_nodename[NODENAME_LEN];
  1397.  
  1398.   if (nodename && *nodename)
  1399.     {
  1400.       /* Maybe nodename looks like: (filename)nodename, or worse: (filename).
  1401.          If so, extract the stuff out. */
  1402.       if (*nodename == '(')
  1403.     {
  1404.       register int temp = 1;
  1405.       char character;
  1406.  
  1407.       filename = internal_filename;
  1408.  
  1409.       while ((character = nodename[temp]) && character != ')')
  1410.         {
  1411.           filename[temp - 1] = character;
  1412.           temp++;
  1413.         }
  1414.  
  1415.       if (character)
  1416.         {
  1417.           filename[temp - 1] = '\0';
  1418.           temp++;        /* skip the closing ')' */
  1419.         }
  1420.  
  1421.       /* We have the filename now.  The nodename follows. */
  1422.       internal_nodename[0] = '\0';
  1423.  
  1424.       while (nodename[temp] == ' ' ||
  1425.          nodename[temp] == '\t' ||
  1426.          nodename[temp] == '\n')
  1427.         temp++;
  1428.  
  1429.       if (nodename[temp])
  1430.         {
  1431.           register int tem1 = 0;
  1432.  
  1433.           while (internal_nodename[tem1++] = nodename[temp++])
  1434.         ;
  1435.         }
  1436.       else if (*filename != '\0')
  1437.         strcpy (internal_nodename,"Top");
  1438.  
  1439.       nodename = internal_nodename;
  1440.     }
  1441.     }
  1442.  
  1443.   if (!popping)
  1444.     push_node (current_info_file, current_info_node, pagetop, nodetop);
  1445.  
  1446.   if (!nodename || !*nodename)
  1447.     {
  1448.       nodename = internal_nodename;
  1449.       strcpy (nodename, "Top");
  1450.     }
  1451.  
  1452.   if (!filename || !*filename)
  1453.     {
  1454.       filename = internal_filename;
  1455.       strcpy (filename, current_info_file);
  1456.     }
  1457.  
  1458.   if (!*filename)
  1459.     strcpy (filename, "DIR");
  1460.  
  1461.   if (!get_info_file (filename, 1))
  1462.     goto node_not_found;
  1463.  
  1464.   if (strcmp (nodename, "*") == 0)
  1465.     {
  1466.       /* The "node" that we want is the entire file. */
  1467.       pointer = 0;
  1468.       goto found_node;
  1469.     }
  1470.   
  1471.   /* If we are using a tag table, see if we can find the nodename in it. */
  1472.   if (tag_table)
  1473.     {
  1474.       pointer = find_node_in_tag_table (nodename, 0);
  1475.       if (pointer < 1)
  1476.     {
  1477.       int pop_node ();
  1478.  
  1479.       /* The search through the tag table failed.  Maybe we
  1480.          should try searching the buffer?  Nahh, just barf. */
  1481.     node_not_found:
  1482.       if (popping)
  1483.         return (0);    /* Second time through. */
  1484.  
  1485.       {
  1486.          int save_inhibit = totally_inhibit_errors;
  1487.  
  1488.          totally_inhibit_errors = 0;
  1489.          display_error
  1490.            ("Sorry, unable to find the node \"%s\" in the file \"%s\".",
  1491.         nodename, filename);
  1492.          totally_inhibit_errors = save_inhibit;
  1493.       }
  1494.  
  1495.       current_info_file[0] = '\0';
  1496.       current_info_node[0] = '\0';
  1497.       last_loaded_info_file[0] = '\0';
  1498.       pop_node (internal_filename, internal_nodename, &nodetop, &pagetop);
  1499.       get_node (internal_filename, internal_nodename, 1);
  1500.       return (0);
  1501.     }
  1502.  
  1503.       /* Otherwise, the desired location is right here.
  1504.          Scarf the position byte. */
  1505.       while (tag_table[pointer] != '\177')
  1506.     pointer++;
  1507.  
  1508.       sscanf (&tag_table[pointer + 1], "%d", &pointer);
  1509.  
  1510.       /* Okay, we have a position pointer.  If this is an indirect file,
  1511.          then we should look through the indirect_list for the first
  1512.          element.first_byte which is larger than this.  Then we can load
  1513.          the specified file, and win. */
  1514.       if (indirect)
  1515.     {
  1516.       /* Find the filename for this node. */
  1517.       int idx;
  1518.       for (idx = 0; idx < MAX_INDIRECT_FILES &&
  1519.            indirect_list[idx].filename != (char *) NULL; idx++)
  1520.         {
  1521.           if (indirect_list[idx].first_byte > pointer)
  1522.         {
  1523.           /* We found it. */
  1524.           break;
  1525.         }
  1526.         }
  1527.       if (!get_info_file (indirect_list[idx - 1].filename, 1))
  1528.         goto node_not_found;
  1529.       pointer -= indirect_list[idx - 1].first_byte;
  1530.  
  1531.       /* Here is code to compensate for the header of an indirect file. */
  1532.       {
  1533.         int tt = find_node_start (0);
  1534.         if (tt > -1)
  1535.           pointer += tt;
  1536.       }
  1537.     }
  1538.       else
  1539.     {
  1540.       /* This tag table is *not* indirect.  The filename of the file
  1541.          containing this node is the same as the current file.  The
  1542.          line probably looks like:
  1543.          File: info,  Node: Checking25796 */
  1544.     }
  1545.     }
  1546.   else
  1547.     {
  1548. #if defined (NOTDEF)
  1549.       /* We don't have a tag table.  The node can only be found by
  1550.          searching this file in its entirety.  */
  1551.       if (!get_info_file (filename, 1))
  1552.     return (0);
  1553. #endif /* NOTDEF */
  1554.       pointer = 0;
  1555.     }
  1556.  
  1557.   /* Search this file, using pointer as a good guess where to start. */
  1558.   /* This is the same number that RMS used.  It might be right or wrong. */
  1559.   pointer -= 1000;
  1560.   if (pointer < 0)
  1561.     pointer = 0;
  1562.  
  1563.   pointer = find_node_in_file (nodename, pointer);
  1564.   if (pointer < 0)
  1565.     goto node_not_found;
  1566.  
  1567.   /* We found the node in its file.  Remember exciting information. */
  1568.  
  1569. found_node:
  1570.   back_lines (0, pointer);
  1571.   nodetop = pagetop = pointer;
  1572.   strcpy (current_info_node, nodename);
  1573.   strcpy (current_info_file, filename);
  1574.   get_node_extent ();
  1575.   return (1);
  1576. }
  1577.  
  1578. /* Get the bounds for this node.  NODETOP points to the start of the
  1579.    node. Scan forward looking for info_separator_char, and remember
  1580.    that in NODEBOT. */
  1581. get_node_extent ()
  1582. {
  1583.   int idx = nodetop;
  1584.   int character;
  1585.   int do_it_till_end = (strcmp (current_info_node, "*") == 0);
  1586.  
  1587.   nodelines = 0;
  1588.  
  1589. again:
  1590.   while ((idx < info_buffer_len) &&
  1591.      ((character = info_file[idx]) != info_separator_char))
  1592.     {
  1593.       if (character == '\n')
  1594.     nodelines++;
  1595.       idx++;
  1596.     }
  1597.   if (do_it_till_end && idx != info_buffer_len)
  1598.     {
  1599.       idx++;
  1600.       goto again;
  1601.     }
  1602.   nodebot = idx;
  1603. }
  1604.  
  1605. /* Locate the start of a node in the current search_buffer.  Return
  1606.    the offset to the node start, or minus one.  START is the place in
  1607.    the file at where to begin the search. */
  1608. find_node_start (start)
  1609.      int start;
  1610. {
  1611.   return (search_forward (start_of_node_string, start));
  1612. }
  1613.  
  1614. /* Find NODENAME in TAG_TABLE. */
  1615. find_node_in_tag_table (nodename, offset)
  1616.      char *nodename;
  1617.      int offset;
  1618. {
  1619.   int temp;
  1620.  
  1621.   set_search_constraints (tag_table, tag_buffer_len);
  1622.  
  1623.   temp = offset;
  1624.   while (1)
  1625.     {
  1626.       offset = search_forward (NODE_ID, temp);
  1627.  
  1628.       if (offset < 0)
  1629.     return (offset);
  1630.  
  1631.       temp = skip_whitespace (offset + strlen (NODE_ID));
  1632.  
  1633.       if (strnicmp (tag_table + temp, nodename, strlen (nodename)) == 0)
  1634.     {
  1635.       register char character;
  1636.  
  1637.       character = *(tag_table + temp + strlen (nodename));
  1638.  
  1639.       if (character == '\177' || character == ',')
  1640.         return (temp);
  1641.     }
  1642.     }
  1643. }
  1644.  
  1645. /* Find NODENAME in INFO_FILE. */
  1646. find_node_in_file (nodename, offset)
  1647.      char *nodename;
  1648.      int offset;
  1649. {
  1650.   int temp, last_offset = -1;
  1651.  
  1652.   set_search_constraints (info_file, info_buffer_len);
  1653.  
  1654.   while (1)
  1655.     {
  1656.       offset = find_node_start (offset);
  1657.  
  1658.       if (offset == last_offset)
  1659.     offset = -1;
  1660.       else
  1661.     last_offset = offset;
  1662.  
  1663.       if (offset < 0)
  1664.     return (offset);
  1665.       else
  1666.     temp = forward_lines (1, offset);
  1667.  
  1668.       if (temp == offset)
  1669.     return (-1);        /* At last line now, just a node start. */
  1670.       else
  1671.     offset = temp;
  1672.  
  1673.       temp = string_in_line (NODE_ID, offset);
  1674.  
  1675.       if (temp > -1)
  1676.     {
  1677.       temp = skip_whitespace (temp + strlen (NODE_ID));
  1678.       if (strnicmp (info_file + temp, nodename, strlen (nodename)) == 0)
  1679.         {
  1680.           int check_exact = *(info_file + temp + strlen (nodename));
  1681.  
  1682.           if (check_exact == '\t' ||
  1683.           check_exact == ',' ||
  1684.           check_exact == '.' ||
  1685.           check_exact == '\n')
  1686.         return (offset);
  1687.         }
  1688.     }
  1689.     }
  1690. }
  1691.  
  1692.  
  1693. /* **************************************************************** */
  1694. /*                                    */
  1695. /*            Dumping and Printing Nodes                */
  1696. /*                                    */
  1697. /* **************************************************************** */
  1698.  
  1699. /* Make a temporary filename based on STARTER and the PID of this Info. */
  1700. char *
  1701. make_temp_filename (starter)
  1702.      char *starter;
  1703. {
  1704.   register int i;
  1705.   char *temp;
  1706.  
  1707.   temp = (char *) xmalloc (strlen (starter) + 10);
  1708.   sprintf (temp, "%s-%d", starter, getpid ());
  1709.  
  1710.   for (i = 0; temp[i]; i++)
  1711.     if (!isalnum (temp[i]))
  1712.       temp[i] = '-';
  1713.  
  1714.   return (temp);
  1715. }
  1716.  
  1717. /* Delete a file.  Print errors if necessary. */
  1718. deletefile (filename)
  1719.      char *filename;
  1720. {
  1721.   if (unlink (filename) != 0)
  1722.     {
  1723.       file_error (filename);
  1724.       return (1);
  1725.     }
  1726.   return (0);
  1727. }
  1728.  
  1729. printfile (filename)
  1730.      char *filename;
  1731. {
  1732.   int length = strlen (print_command) + strlen (filename) + strlen ("\n") + 1;
  1733.   char *command = (char *) xmalloc (length);
  1734.   int error;
  1735.  
  1736.   display_error ("Printing file `%s'...\n", filename);
  1737.   sprintf (command, "%s %s", print_command, filename);
  1738.   error = system (command);
  1739.   if (error)
  1740.     display_error ("Can't invoke `%s'", command);
  1741.   free (command);
  1742.   return (error);
  1743. }
  1744.  
  1745. /* Dump the current node into a file named FILENAME.
  1746.    Return 0 if the dump was successful, otherwise,
  1747.    print error and exit. */
  1748. dump_current_node (filename)
  1749.      char *filename;
  1750. {
  1751.   int c, i = nodetop;
  1752.   FILE *output_stream;
  1753.  
  1754.   if (strcmp (filename, "-") == 0)
  1755.     output_stream = stdout;
  1756.   else
  1757.     output_stream = fopen (filename, "w");
  1758.  
  1759.   if (output_stream == (FILE *) NULL)
  1760.     {
  1761.       file_error (filename);
  1762.       return (1);
  1763.     }
  1764.  
  1765.   while (i < nodebot && i < info_buffer_len)
  1766.     {
  1767.       c = info_file[i];
  1768.       if (CTRL_P (c) && !(index ("\n\t\f", c)))
  1769.     {
  1770.       putc ('^', output_stream);
  1771.       c = UNCTRL (c);
  1772.     }
  1773.  
  1774.       if (putc (c, output_stream) == EOF)
  1775.     {
  1776.       if (output_stream != stdout)
  1777.         fclose (output_stream);
  1778.  
  1779.       file_error (filename);
  1780.       return (1);
  1781.     }
  1782.       i++;
  1783.     }
  1784.  
  1785.   if (output_stream != stdout)
  1786.     fclose (output_stream);
  1787.  
  1788.   return (0);
  1789. }
  1790.  
  1791. /* **************************************************************** */
  1792. /*                                    */
  1793. /*             Toplevel eval loop.                 */
  1794. /*                                    */
  1795. /* **************************************************************** */
  1796.  
  1797. #define MENU_HEADER "\n* Menu:"
  1798. #define MENU_ID "\n* "
  1799. #define FOOTNOTE_HEADER "*Note"
  1800.  
  1801. /* Number of items that the current menu has. */
  1802. int the_menu_size = 0;
  1803.  
  1804. /* The node that last made a menus completion list. */
  1805. char menus_nodename[NODENAME_LEN];
  1806. char menus_filename[NODENAME_LEN];
  1807.  
  1808. static int search_start = 0;
  1809.  
  1810. /* The default prompt string for the Follow Reference command. */
  1811. char *visible_footnote = (char *)NULL;
  1812. toploop ()
  1813. {
  1814.   int done, inhibit_display;
  1815.   int command, last_command;
  1816.   int last_pointer, count, new_ypos, last_pagetop;
  1817.   char nodename[NODENAME_LEN];
  1818.  
  1819.   done = inhibit_display = 0;
  1820.   command = last_command = 0;
  1821.   new_ypos = last_pagetop = -1;
  1822.  
  1823.   while (!done)
  1824.     {
  1825.       if (!inhibit_display &&
  1826.       (window_bashed || (pagetop != last_pagetop)))
  1827.     display_page ();
  1828.  
  1829.       inhibit_display = window_bashed = 0;
  1830.       last_pagetop = pagetop;
  1831.  
  1832.       nodename[0] = '\0';    /* Don't display old text in input line. */
  1833.  
  1834.       last_command = command;
  1835.  
  1836.       if (last_command == 'S')
  1837.     cursor_to (search_start);
  1838.       else
  1839.     goto_xy (echo_area.left, echo_area.top);
  1840.  
  1841.       command = blink_cursor ();
  1842.       clear_echo_area ();
  1843.  
  1844.       if (command == EOF)
  1845.     {
  1846.       done = 1;
  1847.       continue;
  1848.     }
  1849.       command = to_upper (command);
  1850.  
  1851.       switch (command)
  1852.     {
  1853.     case 'D':
  1854.       get_node ((char *) NULL, "(dir)Top", 0);
  1855.       break;
  1856.  
  1857.     case 'H':
  1858.       if ((the_window.bottom - the_window.top) < 24)
  1859.         get_node ((char *) NULL, "(info)Help-Small-Screen", 0);
  1860.       else
  1861.         get_node ((char *) NULL, "(info)Help", 0);
  1862.       break;
  1863.  
  1864.     case 'N':
  1865.       if (!next_node ())
  1866.         {
  1867.           display_error ("No NEXT for this node!");
  1868.           inhibit_display = 1;
  1869.         }
  1870.       break;
  1871.  
  1872.     case 'P':
  1873.       if (!prev_node ())
  1874.         {
  1875.           display_error ("No PREV for this node!");
  1876.           inhibit_display = 1;
  1877.         }
  1878.       break;
  1879.  
  1880.     case 'U':
  1881.       {
  1882.         int savetop = pagetop;
  1883.  
  1884.         if (!up_node ())
  1885.           {
  1886.         display_error ("No UP for this node!");
  1887.         inhibit_display = 1;
  1888.         pagetop = savetop;
  1889.           }
  1890.         break;
  1891.       }
  1892.  
  1893.     case 'M':
  1894.       if (!build_menu ())
  1895.         {
  1896.           display_error ("No menu in this node!");
  1897.           inhibit_display = 1;
  1898.           break;
  1899.         }
  1900.  
  1901.       if (!readline ("Menu item: ", nodename, NODENAME_LEN, 1))
  1902.         {
  1903.           clear_echo_area ();
  1904.           inhibit_display = 1;
  1905.           break;
  1906.         }
  1907.  
  1908.       I_goto_xy (echo_area.left, echo_area.top);
  1909.       if (!find_menu_node (nodename, nodename))
  1910.         {
  1911.           display_error ("\"%s\" is not a menu item!", nodename);
  1912.           inhibit_display = 1;
  1913.           break;
  1914.         }
  1915.  
  1916.       if (get_node ((char *) NULL, nodename, 0))
  1917.         clear_echo_area ();
  1918.       break;
  1919.  
  1920.     case 'F':
  1921.       {
  1922.         char footnote[NODENAME_LEN];
  1923.  
  1924.         if (!build_notes ())
  1925.           {
  1926.         display_error ("No cross-references in this node!");
  1927.         inhibit_display = 1;
  1928.         break;
  1929.           }
  1930.  
  1931.         strcpy (footnote, visible_footnote);
  1932.         if (!readline ("Follow reference: ", footnote, NODENAME_LEN, 1))
  1933.           {
  1934.         inhibit_display = 1;
  1935.         break;
  1936.           }
  1937.  
  1938.         I_goto_xy (echo_area.left, echo_area.top);
  1939.         if (!find_note_node (footnote, nodename))
  1940.           {
  1941.         display_error ("\"%s\" is not a cross-reference in this node!",
  1942.                    footnote);
  1943.         inhibit_display = 1;
  1944.         break;
  1945.           }
  1946.  
  1947.         if (get_node ((char *)NULL, nodename, 0))
  1948.           clear_echo_area ();
  1949.         break;
  1950.       }
  1951.  
  1952.     case 'L':
  1953.       {
  1954.         char filename[FILENAME_LEN], nodename[NODENAME_LEN];
  1955.         int ptop, ntop;
  1956.         if (pop_node (filename, nodename, &ntop, &ptop) &&
  1957.         get_node (filename, nodename, 1))
  1958.           {
  1959.         pagetop = ptop;
  1960.           }
  1961.         else
  1962.           inhibit_display = 1;
  1963.         break;
  1964.       }
  1965.  
  1966.     case SPACE:
  1967.     case CTRL ('V'):
  1968.       if (!next_page ())
  1969.         {
  1970.           display_error ("At last page of this node now!");
  1971.           inhibit_display = 1;
  1972.         }
  1973.       break;
  1974.  
  1975.     case META ('V'):
  1976.     case DELETE:
  1977.       if (!prev_page ())
  1978.         {
  1979.           display_error ("At first page of this node now!");
  1980.           inhibit_display = 1;
  1981.         }
  1982.       break;
  1983.  
  1984.     case 'B':
  1985.       if (pagetop == nodetop)
  1986.         {
  1987.           display_error ("Already at beginning of this node!");
  1988.           inhibit_display = 1;
  1989.         }
  1990.       else
  1991.         pagetop = nodetop;
  1992.       break;
  1993.  
  1994.       /* I don't want to do this this way, but the documentation
  1995.          clearly states that '6' doesn't work.  It states this for a
  1996.          reason, and ours is not to wonder why... */
  1997.     case '1':
  1998.     case '2':
  1999.     case '3':
  2000.     case '4':
  2001.     case '5':
  2002.       {
  2003.         int item = command - '0';
  2004.  
  2005.         if (!build_menu ())
  2006.           {
  2007.         display_error ("No menu in this node!");
  2008.         inhibit_display = 1;
  2009.         break;
  2010.           }
  2011.  
  2012.         if (item > the_menu_size)
  2013.           {
  2014.         display_error ("There are only %d items in the menu!",
  2015.                    the_menu_size);
  2016.         inhibit_display = 1;
  2017.         break;
  2018.           }
  2019.  
  2020.         if (!get_menu (item))
  2021.           inhibit_display = 1;
  2022.       }
  2023.       break;
  2024.  
  2025.     case 'G':
  2026.       if (!readline ("Goto node: ", nodename, NODENAME_LEN, 0))
  2027.         {
  2028.           inhibit_display = 1;
  2029.           break;
  2030.         }
  2031.  
  2032.       if (get_node ((char *) NULL, nodename, 0))
  2033.         clear_echo_area ();
  2034.       break;
  2035.  
  2036.       /* Search from the starting position forward for a string.
  2037.          Select the node containing the desired string.  Put the
  2038.          top of the page screen_lines / 2 lines behind it, but not
  2039.          before nodetop. */
  2040.     case 'S':
  2041.       {
  2042.         int pointer, temp;
  2043.          char prompt[21 + NODENAME_LEN + 1];
  2044.          static char search_string[NODENAME_LEN] = "";
  2045.          static char *starting_filename = NULL,
  2046.                 *starting_nodename = NULL;
  2047.         static int starting_pagetop = 0;
  2048.         static int wrap_search = 0;
  2049.  
  2050.          sprintf (prompt, "Search for string [%s]: ", search_string);
  2051.  
  2052.          if (!readline (prompt , nodename, NODENAME_LEN, 0))
  2053.           {
  2054.         inhibit_display = 1;
  2055.         break;
  2056.           }
  2057.  
  2058.         /* If the user defaulted the search string, and the previous
  2059.            command was search, then this is a continuation of the
  2060.            previous search. */
  2061.         if (((strcmp (nodename, search_string) == 0) ||
  2062.          (!*nodename && *search_string)) &&
  2063.         (last_command == 'S'))
  2064.           {
  2065.         search_start++;
  2066.           }
  2067.         else
  2068.           {
  2069.         /* Initialize the start of a new search. */
  2070.         if (starting_filename)
  2071.           free (starting_filename);
  2072.  
  2073.         starting_filename = savestring (last_loaded_info_file);
  2074.  
  2075.         if (starting_nodename)
  2076.           free (starting_nodename);
  2077.  
  2078.         starting_nodename = savestring (nodename ? nodename : "");
  2079.  
  2080.         starting_pagetop = pagetop;
  2081.         search_start = pagetop;
  2082.         wrap_search = 0;
  2083.  
  2084.         if (*nodename != '\0')
  2085.           strcpy (search_string, nodename);
  2086.           }
  2087.  
  2088.         I_goto_xy (echo_area.left, echo_area.top);
  2089.  
  2090.         {
  2091.           static int pushed = 0; /* How many files are pushed? */
  2092.           int found_string = 0;  /* Did we find our string? */
  2093.  
  2094.           if (wrap_search)
  2095.         {
  2096.           push_filestack (next_info_file ((char *)NULL, 1), 0);
  2097.           pushed++;
  2098.           search_start = 0;
  2099.         }
  2100.  
  2101.           for (;;)
  2102.         {
  2103.           set_search_constraints (info_file, info_buffer_len);
  2104.           pointer = search_forward (search_string, search_start);
  2105.  
  2106.           if (pointer != -1)
  2107.             {
  2108.               found_string = 1;
  2109.               break;
  2110.             }
  2111.           else
  2112.             {
  2113.               char *next_file;
  2114.  
  2115.               next_file = next_info_file ((char *)NULL, 0);
  2116.  
  2117.               if (next_file != NULL)
  2118.             {
  2119.               if (pushed)
  2120.                 {
  2121.                   pop_filestack ();
  2122.                   pushed--;
  2123.                 }
  2124.  
  2125.               push_filestack (next_file, 0);
  2126.               pushed++;
  2127.               search_start = 0;
  2128. #if 1
  2129.               I_goto_xy (echo_area.left, echo_area.top);
  2130.               print_string ("Searching file %s...\n", next_file);
  2131. #endif
  2132.               continue;
  2133.             }
  2134.  
  2135.               if (wrap_search)
  2136.             {
  2137.               display_error
  2138.                 ("\"%s\" not found!",
  2139.                  search_string);
  2140.  
  2141.               inhibit_display = 1;
  2142.               wrap_search = 0;
  2143.  
  2144.               if (pushed)
  2145.                 {
  2146.                   pop_filestack ();
  2147.                   pushed--;
  2148.                 }
  2149.               break;
  2150.             }
  2151.               else
  2152.             {
  2153.               display_error ("Search: End of file");
  2154.               inhibit_display = 1;
  2155.               wrap_search = 1;
  2156.  
  2157.               if (pushed)
  2158.                 {
  2159.                   pop_filestack ();
  2160.                   pushed--;
  2161.                 }
  2162.               break;
  2163.             }
  2164.             }
  2165.         }
  2166.  
  2167.           if (pushed)
  2168.         {
  2169.           swap_filestack ();
  2170.           pop_filestack ();
  2171.           pushed--;
  2172.         }
  2173.  
  2174.           if (!found_string)
  2175.         break;
  2176.  
  2177.           wrap_search = 0;
  2178.           temp = search_backward (start_of_node_string, pointer);
  2179.  
  2180.           if (temp != -1)
  2181.          {
  2182.           search_start = pointer;
  2183.           pointer = forward_lines (1, temp);
  2184.         }
  2185.  
  2186.           if (temp == -1 || !extract_field ("Node:", nodename, pointer))
  2187.         {
  2188.           display_error
  2189.             ("There doesn't appear to be a nodename for this node.");
  2190.           get_node ((char *)NULL, "*", 0);
  2191.           pagetop = pointer;
  2192.           break;
  2193.         }
  2194.          
  2195.           /* Get the node if it is different than the one already
  2196.          loaded. */
  2197.           if (strcmp (nodename, starting_nodename) != 0)
  2198.         {
  2199.           free (starting_nodename);
  2200.           starting_nodename = savestring (nodename);
  2201.  
  2202.           if (get_node ((char *) NULL, nodename, 0))
  2203.             clear_echo_area ();
  2204.         }
  2205.          
  2206.           /* Reset the top of page if necessary. */
  2207.           {
  2208.         if ((strcmp (last_loaded_info_file, starting_filename) != 0) ||
  2209.             (starting_pagetop != pagetop) ||
  2210.             (search_start > pagebot))
  2211.           {
  2212.             pointer =
  2213.               back_lines ((the_window.bottom - the_window.top) / 2,
  2214.                   forward_lines (1, search_start));
  2215.  
  2216.             if (pointer < nodetop)
  2217.               pointer = nodetop;
  2218.            
  2219.             pagetop = pointer;
  2220.             window_bashed = 1;
  2221.           }
  2222.         else
  2223.           inhibit_display = 1;
  2224.           }
  2225.           break;
  2226.         }
  2227.          }
  2228.  
  2229.     case CTRL ('H'):
  2230.     case '?':
  2231.       help_use_info ();
  2232.       last_pagetop = -1;
  2233.       break;
  2234.  
  2235.     case 'Q':
  2236.       done = 1;
  2237.       break;
  2238.  
  2239.     case CTRL ('L'):    /* Control-l is redisplay. */
  2240.       window_bashed = 1;
  2241.       if (last_command == 'S')
  2242.         command = 'S';
  2243.       break;
  2244.  
  2245.     case '(':    /* You *must* be trying to type a complete nodename. */
  2246.       strcpy (nodename, "(");
  2247.       if (!readline ("Goto node: ", nodename, NODENAME_LEN, 0))
  2248.         {
  2249.           inhibit_display = 1;
  2250.           clear_echo_area ();
  2251.           break;
  2252.         }
  2253.       I_goto_xy (echo_area.left, echo_area.top);
  2254.       if (get_node ((char *) NULL, nodename, 0))
  2255.         clear_echo_area ();
  2256.       break;
  2257.  
  2258.     case CTRL ('P'):
  2259.       /* Print the contents of this node on the default printer.  We
  2260.          would like to let the user specify the printer, but we don't
  2261.          want to ask her each time which printer to use.  Besides, he
  2262.          might not know, which is why it (the user) is in the need of
  2263.          Info. */
  2264.       {
  2265.         char *tempname = make_temp_filename (current_info_node);
  2266.         if (dump_current_node (tempname) == 0 &&
  2267.         printfile (tempname) == 0 &&
  2268.         deletefile (tempname) == 0)
  2269.           {
  2270.         display_error ("Printed node.  Go pick up your output.\n");
  2271.           }
  2272.         inhibit_display = 1;
  2273.         free (tempname);
  2274.       }
  2275.       break;
  2276.  
  2277.     default:
  2278.       inhibit_display = 1;
  2279.       display_error ("Unknown command! Press '?' for help.");
  2280.  
  2281.     }
  2282.     }
  2283. }
  2284.  
  2285. /* Return the screen column width that the line from START to END
  2286.    requires to display. */
  2287. line_length (start, end)
  2288.      int start, end;
  2289. {
  2290.   int count = 0;
  2291.  
  2292.   while (start < end)
  2293.     {
  2294.       if (info_file[start] == '\t')
  2295.     count += 7 - (count % 8);
  2296.       else if (CTRL_P (info_file[start]))
  2297.     count += 2;
  2298.       else
  2299.     count++;
  2300.  
  2301.       start++;
  2302.     }
  2303.  
  2304.   return (count);
  2305. }
  2306.  
  2307. /* Tell this person how to use Info. */
  2308. help_use_info ()
  2309. {
  2310.   open_typeout ();
  2311.   clear_screen ();
  2312.   print_string ("\n\
  2313.           Commands in Info\n\
  2314. \n\
  2315. h    Invoke the Info tutorial.\n\
  2316. \n\
  2317. Selecting other nodes:\n\
  2318. n    Move to the \"next\" node of this node.\n\
  2319. p    Move to the \"previous\" node of this node.\n\
  2320. u    Move \"up\" from this node.\n\
  2321. m    Pick menu item specified by name.\n\
  2322.     Picking a menu item causes another node to be selected.\n\
  2323. f    Follow a cross reference.  Reads name of reference.\n\
  2324. l    Move to the last node you were at.\n\
  2325. d    Move to the `directory' node.  Equivalent to `gDIR'.\n\
  2326. \n\
  2327. Moving within a node:\n\
  2328. Space    Scroll forward a page.\n\
  2329. DEL    Scroll backward a page.\n\
  2330. b    Go to the beginning of this node.\n\
  2331. \n\
  2332. Advanced commands:\n\
  2333. q    Quit Info.\n\
  2334. 1    Pick first item in node's menu.\n\
  2335. 2 - 5   Pick second ... fifth item in node's menu.\n\
  2336. g    Move to node specified by name.\n\
  2337.     You may include a filename as well, as (FILENAME)NODENAME.\n\
  2338. s    Search through this Info file for a specified string,\n\
  2339.     and select the node in which the next occurrence is found.\n\
  2340. Ctl-p   Print the contents of this node using `%s'.\n\
  2341. \n\
  2342. Done.\n\n",print_command);
  2343.   close_typeout ();
  2344. }
  2345.  
  2346. /* Move to the node specified in the NEXT field. */
  2347. int
  2348. next_node ()
  2349. {
  2350.   char nodename[NODENAME_LEN];
  2351.  
  2352.   if (!extract_field ("Next:", nodename, nodetop))
  2353.     return (0);
  2354.   return (get_node ((char *) NULL, nodename, 0));
  2355. }
  2356.  
  2357. /* Move to the node specified in the PREVIOUS field. */
  2358. int
  2359. prev_node ()
  2360. {
  2361.   char nodename[NODENAME_LEN];
  2362.  
  2363.   if (!extract_field ("Previous:", nodename, nodetop)
  2364.       && !extract_field ("Prev:", nodename, nodetop))
  2365.     return (0);
  2366.   return (get_node ((char *) NULL, nodename, 0));
  2367. }
  2368.  
  2369. /* Move to the node specified in the UP field. */
  2370. int
  2371. up_node ()
  2372. {
  2373.   char nodename[NODENAME_LEN];
  2374.  
  2375.   if (!extract_field ("Up:", nodename, nodetop))
  2376.     return (0);
  2377.   return (get_node ((char *) NULL, nodename, 0));
  2378. }
  2379.  
  2380. /* Build a completion list of menuname/nodename for each
  2381.    line in this node that is a menu item. */
  2382. int
  2383. build_menu ()
  2384. {
  2385.   int pointer = nodetop;
  2386.   char menuname[NODENAME_LEN];
  2387.   char nodename[NODENAME_LEN];
  2388.  
  2389.   if (strcmp (menus_nodename, current_info_node) == 0 &&
  2390.       strcmp (menus_filename, current_info_file) == 0)
  2391.     return (the_menu_size != 0);
  2392.  
  2393.   strcpy (menus_nodename, current_info_node);
  2394.   strcpy (menus_filename, current_info_file);
  2395.   free_completion_list ();
  2396.   the_menu_size = 0;
  2397.  
  2398.   set_search_constraints (info_file, nodebot);
  2399.   if ((pointer = search_forward (MENU_HEADER, nodetop)) < 0)
  2400.     return (0);
  2401.  
  2402.   /* There is a menu here.  Look for members of it. */
  2403.   pointer += strlen (MENU_HEADER);
  2404.  
  2405.   while (1)
  2406.     {
  2407.       int idx;
  2408.  
  2409.       pointer = search_forward (MENU_ID, pointer);
  2410.       if (pointer < 0)
  2411.     break;            /* No more menus in this node. */
  2412.  
  2413.       pointer = (skip_whitespace (pointer + strlen (MENU_ID)));
  2414.  
  2415.       idx = 0;
  2416.       while ((menuname[idx] = info_file[pointer]) && menuname[idx] != ':')
  2417.     idx++, pointer++;
  2418.  
  2419.       menuname[idx] = '\0';
  2420.       pointer++;
  2421.  
  2422.       if (info_file[pointer] == ':')
  2423.     {
  2424.       strcpy (nodename, menuname);
  2425.     }
  2426.       else
  2427.     {
  2428.       int in_parens;
  2429.  
  2430.       pointer = skip_whitespace (pointer);
  2431.       idx = in_parens = 0;
  2432.  
  2433.       while ((nodename[idx] = info_file[pointer]) &&
  2434.          (in_parens ||
  2435.           (nodename[idx] != '\t' &&
  2436.            nodename[idx] != '.' &&
  2437.            nodename[idx] != ',')))
  2438.         {
  2439.           if (nodename[idx] == '(')
  2440.         in_parens++;
  2441.           else if (nodename[idx] == ')')
  2442.         in_parens--;
  2443.  
  2444.           idx++, pointer++;
  2445.         }
  2446.       nodename[idx] = '\0';
  2447.     }
  2448.       add_completion (menuname, nodename);
  2449.       the_menu_size++;
  2450.     }
  2451.   if (the_menu_size)
  2452.     completion_list = reverse_list (completion_list);
  2453.   return (the_menu_size != 0);
  2454. }
  2455.  
  2456. /* Select ITEMth item from a list built by build_menu (). */
  2457. int
  2458. get_menu (item)
  2459.      int item;
  2460. {
  2461.   if (!build_menu ())
  2462.     return (0);
  2463.  
  2464.   if (item > the_menu_size)
  2465.     return (0);
  2466.   else
  2467.     {
  2468.       COMP_ENTRY *temp = completion_list;
  2469.  
  2470.       while (--item && temp)
  2471.     temp = temp->next;
  2472.  
  2473.       return (get_node ((char *) NULL, temp->data, 0));
  2474.     }
  2475. }
  2476.  
  2477. /* Scan through the ?already? built menu list looking
  2478.    for STRING.  If you find it, put the corresponding nodes
  2479.    name in NODENAME. */
  2480. int
  2481. find_menu_node (string, nodename)
  2482.      char *string, *nodename;
  2483. {
  2484.   return (scan_list (string, nodename));
  2485. }
  2486.  
  2487. /* The work part of find_menu_node and find_note_node. */
  2488. int
  2489. scan_list (string, nodename)
  2490.      char *string, *nodename;
  2491. {
  2492.   COMP_ENTRY *temp = completion_list;
  2493.  
  2494.   while (temp)
  2495.     {
  2496.       if (stricmp (string, temp->identifier, strlen (string)) == 0)
  2497.     {
  2498.       strcpy (nodename, temp->data);
  2499.       return (1);
  2500.     }
  2501.       temp = temp->next;
  2502.     }
  2503.   return (0);
  2504. }
  2505.  
  2506. /* Remove <CR> and whitespace from string, replacing them with
  2507.    only one space.  Exception:  <CR> at end of string disappears. */
  2508. clean_up (string)
  2509.      char *string;
  2510. {
  2511.   char *to;
  2512.  
  2513.   /* Skip all whitespace characters found at the start of STRING. */
  2514.   while (whitespace (*string))
  2515.     string++;
  2516.  
  2517.   to = string;
  2518.  
  2519.   while (*to = *string++)
  2520.     {
  2521.       if (*to == '\n' || *to == ' ')
  2522.     {
  2523.       *to = ' ';
  2524.  
  2525.       while (*string == ' ' || *string == '\t')
  2526.         string++;
  2527.     }
  2528.     to++;
  2529.     }
  2530. }
  2531.  
  2532. /* Find a reference to "*Note".  Return the offset of the start
  2533.    of that reference, or -1. */
  2534. find_footnote_ref (from)
  2535.      int from;
  2536. {
  2537.   while (1)
  2538.     {
  2539.       from = search_forward (FOOTNOTE_HEADER, from);
  2540.       if (from < 0)
  2541.     return (from);
  2542.       else
  2543.     from += strlen (FOOTNOTE_HEADER);
  2544.       if (info_file[from] == ' ' ||
  2545.       info_file[from] == '\n' ||
  2546.       info_file[from] == '\t')
  2547.     return (from);
  2548.     }
  2549. }
  2550.  
  2551. /* Build an array of (footnote.nodename) for each footnote in this node. */
  2552. int
  2553. build_notes ()
  2554. {
  2555.   int pointer;
  2556.   char notename[NODENAME_LEN];
  2557.   char nodename[NODENAME_LEN];
  2558.  
  2559.   set_search_constraints (info_file, nodebot);
  2560.  
  2561.   if ((find_footnote_ref (nodetop)) < 0)
  2562.     return (0);
  2563.   pointer = nodetop;
  2564.  
  2565.   menus_filename[0] = menus_nodename[0] = '\0';
  2566.   visible_footnote = "";
  2567.   free_completion_list ();
  2568.  
  2569.   while (1)
  2570.     {
  2571.       int idx;
  2572.  
  2573.       pointer = find_footnote_ref (pointer);
  2574.       if (pointer < 0)
  2575.     break;            /* no more footnotes in this node. */
  2576.  
  2577.       pointer = skip_whitespace_and_cr (pointer);
  2578.       idx = 0;
  2579.  
  2580.       while ((notename[idx] = info_file[pointer]) && notename[idx] != ':')
  2581.     {
  2582.       idx++, pointer++;
  2583.     }
  2584.  
  2585.       notename[idx] = '\0';
  2586.       clean_up (notename);
  2587.       pointer++;
  2588.       if (info_file[pointer] == ':')
  2589.     {
  2590.       strcpy (nodename, notename);
  2591.     }
  2592.       else
  2593.     {
  2594.       int in_parens = 0;
  2595.  
  2596.       pointer = skip_whitespace (pointer);
  2597.       idx = 0;
  2598.  
  2599.       while ((nodename[idx] = info_file[pointer]) &&
  2600.          (in_parens ||
  2601.           (nodename[idx] != '\t' &&
  2602.            nodename[idx] != '.' &&
  2603.            nodename[idx] != ',')))
  2604.         {
  2605.           if (nodename[idx] == '(')
  2606.         in_parens++;
  2607.           else if (nodename[idx] == ')')
  2608.         in_parens--;
  2609.  
  2610.           idx++, pointer++;
  2611.         }
  2612.       nodename[idx] = '\0';
  2613.       clean_up (nodename);
  2614.     }
  2615.       /* Add the notename/nodename to the list. */
  2616.       add_completion (notename, nodename);
  2617.       the_menu_size++;
  2618.  
  2619.       /* Remember this identifier as the default if it is the first one in the
  2620.          page. */
  2621.       if (!(*visible_footnote) &&
  2622.       pointer > pagetop &&
  2623.       pointer < forward_lines (the_window.bottom - the_window.top, pointer))
  2624.     visible_footnote = completion_list->identifier;
  2625.     }
  2626.   if (the_menu_size)
  2627.     completion_list = reverse_list (completion_list);
  2628.   return (the_menu_size != 0);
  2629. }
  2630.  
  2631. /* Scan through the ?already? built footnote list looking for STRING.
  2632.    If found, place the corresponding node name in NODENAME. */
  2633. int
  2634. find_note_node (string, nodename)
  2635.      char *string, *nodename;
  2636. {
  2637.   return (scan_list (string, nodename));
  2638. }
  2639.  
  2640. /* **************************************************************** */
  2641. /*                                    */
  2642. /*            Page Display                     */
  2643. /*                                    */
  2644. /* **************************************************************** */
  2645.  
  2646.  
  2647. /* The display functions for GNU Info. */
  2648. int display_ch, display_cv;
  2649. int display_point;
  2650.  
  2651. /* Display the current page from pagetop down to the bottom of the
  2652.    page or the bottom of the node, whichever comes first. */
  2653. display_page ()
  2654. {
  2655.   display_point = pagetop;
  2656.   display_ch = the_window.left;
  2657.   display_cv = the_window.top;
  2658.   generic_page_display ();
  2659. }
  2660.  
  2661. /* Print the page from display_point to bottom of node, or window,
  2662.    whichever comes first.  Start printing at display_ch, display_cv. */
  2663. generic_page_display ()
  2664. {
  2665.   int done_with_display = 0;
  2666.   int character;
  2667.  
  2668.   goto_xy (display_ch, display_cv);
  2669.  
  2670.   while (!done_with_display)
  2671.     {
  2672.       if (display_point == nodebot)
  2673.     {
  2674.       clear_eop ();
  2675.       goto display_finish;
  2676.     }
  2677.  
  2678.       character = info_file[display_point];
  2679.  
  2680.       if ((display_width (character, the_window.ch) + the_window.ch)
  2681.       >= the_window.right)
  2682.     display_carefully (character);
  2683.       else
  2684.     charout (character);
  2685.  
  2686.       if ((the_window.cv >= the_window.bottom)
  2687.       || (the_window.cv == the_window.top
  2688.           && the_window.ch == the_window.left))
  2689.     {
  2690.     display_finish:
  2691.       pagebot = display_point;
  2692.       make_modeline ();
  2693.       done_with_display++;
  2694.       continue;
  2695.     }
  2696.       else
  2697.     display_point++;
  2698.     }
  2699.   fflush (stdout);
  2700. }
  2701.  
  2702. /* Display character carefully, ensuring that no scrolling takes
  2703.    place, even in the case of funky control characters. */
  2704. display_carefully (character)
  2705.      int character;
  2706. {
  2707.   if (CTRL_P (character))
  2708.     {
  2709.       switch (character)
  2710.     {
  2711.     case RETURN:
  2712.     case NEWLINE:
  2713.     case TAB:
  2714.       clear_eol ();
  2715.       advance (the_window.right - the_window.ch);
  2716.       break;
  2717.     default:
  2718.       charout ('^');
  2719.       if (the_window.cv == the_window.bottom)
  2720.         break;
  2721.       else
  2722.         charout (UNCTRL (character));
  2723.     }
  2724.     }
  2725.   else
  2726.     charout (character);
  2727. }
  2728.  
  2729. /* Move the cursor to POSITION in page.  Return non-zero if successful. */
  2730. cursor_to (position)
  2731.      int position;
  2732. {
  2733.   int ch, cv, character;
  2734.   int point;
  2735.  
  2736.   if (position > pagebot || position < pagetop)
  2737.     return (0);
  2738.  
  2739.   point = pagetop;
  2740.   ch = the_window.left;
  2741.   cv = the_window.top;
  2742.  
  2743.   while (point < position)
  2744.     {
  2745.       character = info_file[point++];
  2746.  
  2747.       ch += display_width (character, ch);
  2748.  
  2749.       if (ch >= the_window.right)
  2750.     {
  2751.       ch = ch - the_window.right;
  2752.       cv++;
  2753.  
  2754.       if (cv >= the_window.bottom)
  2755.         return (0);
  2756.     }
  2757.     }
  2758.   goto_xy (ch, cv);
  2759.   return (1);
  2760. }
  2761.  
  2762. /* Move to the next page in this node.  Return 0 if
  2763.    we can't get to the next page. */
  2764. int
  2765. next_page ()
  2766. {
  2767.   int pointer;
  2768.  
  2769.   pointer =
  2770.     forward_lines ((the_window.bottom - the_window.top) - 2, pagetop);
  2771.  
  2772.   if (pointer >= nodebot)
  2773.     return (0);
  2774.  
  2775.   /* Hack for screens smaller than displayed line width. */
  2776.   if (pointer > display_point)
  2777.     {
  2778.       pointer = display_point;
  2779.       back_lines (1, pointer);
  2780.     }
  2781.   pagetop = pointer;
  2782.   return (1);
  2783. }
  2784.  
  2785. /* Move to the previous page in this node.  Return zero if
  2786.    there is no previous page. */
  2787. int
  2788. prev_page ()
  2789. {
  2790.   int pointer =
  2791.   back_lines ((the_window.bottom - the_window.top) - 2, pagetop);
  2792.  
  2793.   if (pagetop == nodetop)
  2794.     return (0);
  2795.  
  2796.   if (pointer < nodetop)
  2797.     pointer = nodetop;
  2798.  
  2799.   pagetop = pointer;
  2800.   return (1);
  2801. }
  2802.  
  2803.  
  2804. /* **************************************************************** */
  2805. /*                                    */
  2806. /*            Utility Functions                */
  2807. /*                                    */
  2808. /* **************************************************************** */
  2809.  
  2810. char *search_buffer;        /* area in ram to scan through. */
  2811. int buffer_bottom;        /* Length of this area. */
  2812.  
  2813. /* Set the global variables that all of these routines use. */
  2814. set_search_constraints (buffer, extent)
  2815.      char *buffer;
  2816.      int extent;
  2817. {
  2818.   search_buffer = buffer;
  2819.   buffer_bottom = extent;
  2820. }
  2821.  
  2822. /* Move back to the start of this line. */
  2823. to_beg_line (from)
  2824.      int from;
  2825. {
  2826.   while (from && search_buffer[from - 1] != '\n')
  2827.     from--;
  2828.   return (from);
  2829. }
  2830.  
  2831. /* Move forward to the end of this line. */
  2832. to_end_line (from)
  2833. {
  2834.   while (from < buffer_bottom && search_buffer[from] != '\n')
  2835.     from++;
  2836.   return (from);
  2837. }
  2838.  
  2839. /* Move back count lines in search_buffer starting at starting_pos.
  2840.    Returns the start of that line. */
  2841. back_lines (count, starting_pos)
  2842.      int count, starting_pos;
  2843. {
  2844.   starting_pos = to_beg_line (starting_pos);
  2845.   while (starting_pos && count)
  2846.     {
  2847.       starting_pos = to_beg_line (starting_pos - 1);
  2848.       count--;
  2849.     }
  2850.   return (starting_pos);
  2851. }
  2852.  
  2853. /* Move forward count lines starting at starting_pos.
  2854.    Returns the start of that line. */
  2855. forward_lines (count, starting_pos)
  2856.      int count, starting_pos;
  2857. {
  2858.   starting_pos = to_end_line (starting_pos);
  2859.   while (starting_pos < buffer_bottom && count)
  2860.     {
  2861.       starting_pos = to_end_line (starting_pos + 1);
  2862.       count--;
  2863.     }
  2864.   return (to_beg_line (starting_pos));
  2865. }
  2866.  
  2867. /* Search for STRING in SEARCH_BUFFER starting at STARTING_POS.
  2868.    Return the location of the string, or -1 if not found. */
  2869. search_forward (string, starting_pos)
  2870.      char *string;
  2871.      int starting_pos;
  2872. {
  2873.   register int c, i, len;
  2874.   register char *buff, *end;
  2875.   char *alternate;
  2876.  
  2877.  
  2878.   /* We match characters in SEARCH_BUFFER against STRING and ALTERNATE.
  2879.      ALTERNATE is a case reversed version of STRING; this is cheaper than
  2880.      case folding each character before comparison. */
  2881.  
  2882.   /* Build the alternate string. */
  2883.   alternate = savestring (string);
  2884.   len = strlen (string);
  2885.  
  2886.   for (i = 0; i < len; i++)
  2887.     {
  2888.       c = alternate[i];
  2889.  
  2890.       if (c >= 'a' && c <= 'z')
  2891.     alternate[i] = c - 32;
  2892.       else if (c >= 'A' && c <= 'Z')
  2893.     alternate[i] = c + 32;
  2894.     }
  2895.  
  2896.   buff = search_buffer + starting_pos;
  2897.   end = search_buffer + buffer_bottom + 1;
  2898.  
  2899.   while (buff < end)
  2900.     {
  2901.       for (i = 0; i < len; i++)
  2902.     {
  2903.       c = buff[i];
  2904.  
  2905.       if (c != string[i] && c != alternate[i])
  2906.         break;
  2907.     }
  2908.  
  2909.       if (!string[i])
  2910.     {
  2911.       free (alternate);
  2912.       return (buff - search_buffer);
  2913.     }
  2914.  
  2915.       buff++;
  2916.     }
  2917.  
  2918.   free (alternate);
  2919.   return (-1);
  2920. }
  2921.  
  2922. /* Search for STRING in SEARCH_BUFFER starting at STARTING_POS.
  2923.    Return the location of the string, or -1 if not found. */
  2924. search_backward (string, starting_pos)
  2925.      char *string;
  2926.      int starting_pos;
  2927. {
  2928.   int len = strlen (string);
  2929.   while (starting_pos - len > -1)
  2930.     {
  2931.       if (strnicmp (search_buffer + (starting_pos - len), string, len) == 0)
  2932.     return (starting_pos - len);
  2933.       else
  2934.     starting_pos--;
  2935.     }
  2936.   return (-1);
  2937. }
  2938.  
  2939. /* Only search for STRING from POINTER to end of line.  Return offset
  2940.    of string, or -1 if not found. */
  2941. string_in_line (string, pointer)
  2942.      char *string;
  2943.      int pointer;
  2944. {
  2945.   int old_buffer_bottom = buffer_bottom;
  2946.  
  2947.   set_search_constraints (search_buffer, to_end_line (pointer));
  2948.   pointer = search_forward (string, pointer);
  2949.   buffer_bottom = old_buffer_bottom;
  2950.   return (pointer);
  2951. }
  2952.  
  2953. /* Skip whitespace characters at OFFSET in SEARCH_BUFFER.
  2954.    Return the next non-whitespace character or -1 if BUFFER_BOTTOM
  2955.    is reached. */
  2956. skip_whitespace (offset)
  2957.      int offset;
  2958. {
  2959.   int character;
  2960.  
  2961.   while (offset < buffer_bottom)
  2962.     {
  2963.       character = search_buffer[offset];
  2964.       if (character == ' ' || character == '\t')
  2965.     offset++;
  2966.       else
  2967.     return (offset);
  2968.     }
  2969.   return (-1);
  2970. }
  2971.  
  2972. /* Skip whitespace characters including <CR> at OFFSET in
  2973.    SEARCH_BUFFER.  Return the position of the next non-whitespace
  2974.    character, or -1 if BUFFER_BOTTOM is reached. */
  2975. skip_whitespace_and_cr (offset)
  2976.      int offset;
  2977. {
  2978.   while (1)
  2979.     {
  2980.       offset = skip_whitespace (offset);
  2981.       if (offset > 0 && search_buffer[offset] != '\n')
  2982.     return (offset);
  2983.       else
  2984.     offset++;
  2985.     }
  2986. }
  2987.  
  2988. /* Extract the node name part of the of the text after the FIELD.
  2989.    Place the node name into NODENAME.  Assume the line starts at
  2990.    OFFSET in SEARCH_BUFFER. */
  2991. int
  2992. extract_field (field_name, nodename, offset)
  2993.      char *field_name, *nodename;
  2994.      int offset;
  2995. {
  2996.   int temp, character;
  2997.  
  2998.   temp = string_in_line (field_name, offset);
  2999.   if (temp < 0)
  3000.     return (0);
  3001.  
  3002.   temp += strlen (field_name);
  3003.   temp = skip_whitespace (temp);
  3004.  
  3005.   /* Okay, place the following text into NODENAME. */
  3006.  
  3007.   while ((character = search_buffer[temp]) != ','
  3008.      && character != '\n'
  3009.      && character != '\t')
  3010.     {
  3011.       *nodename = character;
  3012.       nodename++;
  3013.       temp++;
  3014.     }
  3015.   *nodename = '\0';
  3016.   return (1);
  3017. }
  3018.  
  3019. /* Return non-zero if pointer is exactly at string, else zero. */
  3020. int
  3021. looking_at (string, pointer)
  3022.      char *string;
  3023.      int pointer;
  3024. {
  3025.   if (strnicmp (search_buffer + pointer, string, strlen (string)) == 0)
  3026.     return (1);
  3027.   else
  3028.     return (0);
  3029. }
  3030.  
  3031. /* File stack stuff. This is currently only used to push one file while
  3032.    searching indirect files, but we may as well write it in full
  3033.    generality. */
  3034. typedef struct filestack
  3035. {
  3036.   struct filestack *next;
  3037.   char filename[FILENAME_LEN];
  3038.   char current_filename[FILENAME_LEN];
  3039.   char *tag_table;
  3040.   char *info_file;
  3041.   int info_buffer_len;
  3042. } FILESTACK;
  3043.  
  3044. FILESTACK *filestack = NULL;
  3045.  
  3046. int
  3047. push_filestack (filename, remember_name)
  3048.      char *filename;
  3049.      int remember_name;
  3050. {
  3051.   FILESTACK *element = (FILESTACK *) xmalloc (sizeof (FILESTACK));
  3052.  
  3053.   element->next = filestack;
  3054.   filestack = element;
  3055.  
  3056.   strcpy (filestack->filename, last_loaded_info_file);
  3057.   strcpy (filestack->current_filename, current_info_file);
  3058.   filestack->tag_table = tag_table;
  3059.   filestack->info_file = info_file;
  3060.   filestack->info_buffer_len = info_buffer_len;
  3061.   
  3062.   *last_loaded_info_file = '\0';    /* force the file to be read */
  3063.   info_file = (char *)NULL;    /* Pretend we have no buffer. */
  3064.   if (get_info_file (filename, remember_name))
  3065.     {
  3066.       return (1);
  3067.     }
  3068.   else
  3069.     {
  3070.       pop_filestack ();
  3071.       return (0);
  3072.     }
  3073. }
  3074.  
  3075. void
  3076. pop_filestack ()
  3077. {
  3078.   FILESTACK *temp;
  3079.    
  3080.   if (filestack == NULL)
  3081.     {
  3082.        fprintf (stderr , "File stack is empty and can't be popped\n");
  3083.        brians_error ();
  3084.        return;
  3085.     }
  3086.  
  3087.   free (info_file);
  3088.  
  3089.   strcpy (last_loaded_info_file, filestack->filename);
  3090.   strcpy (current_info_file, filestack->current_filename);
  3091.   tag_table = filestack->tag_table;
  3092.   info_file = filestack->info_file;
  3093.   info_buffer_len = filestack->info_buffer_len;
  3094.   set_search_constraints (info_file, info_buffer_len);
  3095.  
  3096.   temp = filestack;
  3097.   filestack = filestack->next;
  3098.   free ((char *)temp);
  3099. }
  3100.  
  3101. /* Swap the current info file with the bottom of the filestack */
  3102. void
  3103. swap_filestack ()
  3104. {
  3105.   char t_last_loaded_info_file[FILENAME_LEN];
  3106.   char t_current_info_file[FILENAME_LEN];
  3107.   char *t_tag_table;
  3108.   char *t_info_file;
  3109.   int t_info_buffer_len;
  3110.  
  3111.   if (filestack == NULL)
  3112.     {
  3113.        fprintf (stderr , "File stack is empty and can't be swapped\n");
  3114.        brians_error ();
  3115.        return;
  3116.     }
  3117.  
  3118.   strcpy (t_last_loaded_info_file, filestack->filename);
  3119.   strcpy (t_current_info_file, filestack->current_filename);
  3120.   t_tag_table = filestack->tag_table;
  3121.   t_info_file = info_file;
  3122.   t_info_buffer_len = info_buffer_len;
  3123.  
  3124.   strcpy (filestack->filename, last_loaded_info_file);
  3125.   strcpy (filestack->current_filename, current_info_file);
  3126.   filestack->tag_table = tag_table;
  3127.   filestack->info_file = info_file;
  3128.   filestack->info_buffer_len = info_buffer_len;
  3129.  
  3130.   strcpy (last_loaded_info_file, t_last_loaded_info_file);
  3131.   strcpy (current_info_file, t_current_info_file);
  3132.   tag_table = t_tag_table;
  3133.   info_file = t_info_file;
  3134.   info_buffer_len = t_info_buffer_len;
  3135. }
  3136.  
  3137. /* Now the node history stack */
  3138.  
  3139. extern NODEINFO *Info_History;
  3140.  
  3141. /* Save the current filename, nodename, and position on the history list.
  3142.    We prepend. */
  3143. int
  3144. push_node (filename, nodename, page_position, node_position)
  3145.      char *filename, *nodename;
  3146.      int page_position, node_position;
  3147. {
  3148.   NODEINFO *newnode = (NODEINFO *) xmalloc (sizeof (NODEINFO));
  3149.  
  3150.   newnode->next = Info_History;
  3151.  
  3152.   newnode->filename = (char *) xmalloc (strlen (filename) + 1);
  3153.   strcpy (newnode->filename, filename);
  3154.  
  3155.   newnode->nodename = (char *) xmalloc (strlen (nodename) + 1);
  3156.   strcpy (newnode->nodename, nodename);
  3157.  
  3158.   newnode->pagetop = page_position;
  3159.   newnode->nodetop = node_position;
  3160.  
  3161.   Info_History = newnode;
  3162.   return (1);
  3163. }
  3164.  
  3165. /* Pop one node from the node list, leaving the values in
  3166.    passed variables. */
  3167. int
  3168. pop_node (filename, nodename, nodetop, pagetop)
  3169.      char *filename, *nodename;
  3170.      int *nodetop, *pagetop;
  3171. {
  3172.   if (Info_History->next == (NODEINFO *) NULL)
  3173.     {
  3174.       display_error ("At beginning of history now!");
  3175.       return (0);
  3176.     }
  3177.   else
  3178.     {
  3179.       NODEINFO *releaser = Info_History;
  3180.  
  3181.     /* If the popped file is not the current file, then force
  3182.        the popped file to be loaded. */
  3183.       if (strcmp (Info_History->filename, last_loaded_info_file) != 0)
  3184.     last_loaded_info_file[0] = '\0';
  3185.  
  3186.       strcpy (filename, Info_History->filename);
  3187.       strcpy (nodename, Info_History->nodename);
  3188.       *pagetop = Info_History->pagetop;
  3189.       *nodetop = Info_History->nodetop;
  3190.       free (Info_History->nodename);
  3191.       free (Info_History->filename);
  3192.       Info_History = Info_History->next;
  3193.       free (releaser);
  3194.       return (1);
  3195.     }
  3196. }
  3197.  
  3198. /* Whoops, Unix doesn't have strnicmp. */
  3199.  
  3200. /* Compare at most COUNT characters from string1 to string2.  Case
  3201.    doesn't matter. */
  3202. int
  3203. strnicmp (string1, string2, count)
  3204.      char *string1, *string2;
  3205. {
  3206.   char ch1, ch2;
  3207.  
  3208.   while (count)
  3209.     {
  3210.       ch1 = *string1++;
  3211.       ch2 = *string2++;
  3212.       if (to_upper (ch1) == to_upper (ch2))
  3213.     count--;
  3214.       else
  3215.     break;
  3216.     }
  3217.   return (count);
  3218. }
  3219.  
  3220. /* Compare string1 to string2.  Case doesn't matter. */
  3221. int
  3222. stricmp (string1, string2)
  3223.      char *string1, *string2;
  3224. {
  3225.   char ch1, ch2;
  3226.  
  3227.   while (1)
  3228.     {
  3229.       ch1 = *string1++;
  3230.       ch2 = *string2++;
  3231.  
  3232.       if (ch1 == '\0')
  3233.     return (ch2 != '\0');
  3234.  
  3235.       if ((ch2 == '\0') ||
  3236.       (to_upper (ch1) != to_upper (ch2)))
  3237.     return (1);
  3238.     }
  3239. }
  3240.  
  3241. /* Make the user type "Y" or "N". */
  3242. int 
  3243. get_y_or_n_p ()
  3244. {
  3245.   int character;
  3246.   print_string (" (Y or N)?");
  3247.   clear_eol ();
  3248.  
  3249. until_we_like_it:
  3250.  
  3251.   character = blink_cursor ();
  3252.   if (character == EOF)
  3253.     return (0);
  3254.   if (to_upper (character) == 'Y')
  3255.     {
  3256.       charout (character);
  3257.       return (1);
  3258.     }
  3259.  
  3260.   if (to_upper (character) == 'N')
  3261.     {
  3262.       charout (character);
  3263.       return (0);
  3264.     }
  3265.  
  3266.   if (character == ABORT_CHAR)
  3267.     {
  3268.       ding ();
  3269.       return (0);
  3270.     }
  3271.  
  3272.   goto until_we_like_it;
  3273. }
  3274.  
  3275. /* Move the cursor to the desired column in the window. */
  3276. indent_to (screen_column)
  3277.      int screen_column;
  3278. {
  3279.   int counter = screen_column - the_window.ch;
  3280.   if (counter > 0)
  3281.     {
  3282.       while (counter--)
  3283.     charout (' ');
  3284.     }
  3285.   else if (screen_column != 0)
  3286.     charout (' ');
  3287. }
  3288.  
  3289.  
  3290. /* **************************************************************** */
  3291. /*                                    */
  3292. /*            Error output/handling.                */
  3293. /*                                    */
  3294. /* **************************************************************** */
  3295.  
  3296. /* Display specific error from known file error table. */
  3297. file_error (file)
  3298.      char *file;
  3299. {
  3300.   extern int errno;
  3301.   extern int sys_nerr;
  3302.   extern char *sys_errlist[];
  3303.  
  3304.   if (errno < sys_nerr)
  3305.     display_error ("%s: %s", file, sys_errlist[errno]);
  3306.   else
  3307.     display_error ("%s: Unknown error %d", file, errno);
  3308. }
  3309.  
  3310. /* Display the error in the echo-area using format_string and args.
  3311.    This is a specialized interface to printf. */
  3312. display_error (format_string, arg1, arg2)
  3313.      char *format_string;
  3314. {
  3315.   extern int terminal_inited_p;
  3316.   char output_buffer[1024];
  3317.  
  3318.   if (totally_inhibit_errors)
  3319.     return;
  3320.  
  3321.   sprintf (output_buffer, format_string, arg1, arg2);
  3322.   if (terminal_inited_p)
  3323.     {
  3324.       new_echo_area ();
  3325.       ding ();
  3326.       print_string ("%s", output_buffer);
  3327.       close_echo_area ();
  3328.     }
  3329.   else
  3330.     {
  3331.       fprintf (stderr, "%s\n", output_buffer);
  3332.     }
  3333. }
  3334.  
  3335. /* Tell everybody what a loser I am.  If you see this error,
  3336.    send me a bug report. */
  3337. brians_error ()
  3338. {
  3339.   display_error ("You are never supposed to see this error.\n");
  3340.   display_error ("Tell bfox@ai.mit.edu to fix this someday.\n");
  3341.   return (-1);
  3342. }
  3343.  
  3344. /* **************************************************************** */
  3345. /*                                    */
  3346. /*            Terminal IO, and Driver                */
  3347. /*                                    */
  3348. /* **************************************************************** */
  3349.  
  3350. /* The Unix termcap interface code. */
  3351.  
  3352. #define NO_ERROR 0
  3353. #define GENERIC_ERROR 1
  3354. #define NO_TERMINAL_DESCRIPTOR 2
  3355. #define OUT_OF_MEMORY 3
  3356. #define BAD_TERMINAL 4
  3357.  
  3358. #define FERROR(msg)    fprintf (stderr, msg); exit (GENERIC_ERROR)
  3359.  
  3360. extern int tgetnum (), tgetflag ();
  3361. extern char *tgetstr ();
  3362. extern char *tgoto ();
  3363.  
  3364. #define Certainly_enough_space 2048    /* page 3, Section 1.1, para 4 */
  3365.  
  3366. #if defined (unix)
  3367. char termcap_buffer[Certainly_enough_space];
  3368. #else /* !unix */
  3369. #define termcap_buffer NULL
  3370. #endif /* !unix */
  3371.  
  3372. /* You CANNOT remove these next four vars.  TERMCAP needs them to operate. */
  3373. char PC;
  3374. char *BC;
  3375. char *UP;
  3376.  
  3377. /* A huge array of stuff to get from termcap initialization. */
  3378.  
  3379. #define tc_int 0
  3380. #define tc_char tc_int+1
  3381. #define tc_flag tc_char+1
  3382. #define tc_last tc_flag+1
  3383.  
  3384. typedef int flag;
  3385.  
  3386. /* First, the variables which this array refers to */
  3387.  
  3388. /* Capabilities */
  3389.  
  3390. int terminal_columns;        /* {tc_int, "co" */
  3391. int terminal_rows;        /* {tc_int, "li" */
  3392. flag terminal_is_generic;    /* {tc_flag,"gn" */
  3393.  
  3394.  /* Cursor Motion */
  3395.  
  3396. char *terminal_goto;        /* {tc_char,"cm" */
  3397. char *terminal_home;        /* {tc_char,"ho" */
  3398.  
  3399. char *terminal_cursor_left;    /* {tc_char,"le" */
  3400. char *terminal_cursor_right;    /* {tc_char,"nd" */
  3401. char *terminal_cursor_up;    /* {tc_char,"up" */
  3402. char *terminal_cursor_down;    /* {tc_char,"do" */
  3403.  
  3404. /* Screen Clearing */
  3405.  
  3406. char *terminal_clearpage;    /* {tc_char,"cl" */
  3407. char *terminal_clearEOP;    /* {tc_char,"cd" */
  3408. char *terminal_clearEOL;    /* {tc_char,"ce" */
  3409.  
  3410. /* "Standout" */
  3411. char *terminal_standout_begin;    /* {tc_char,"so" */
  3412. char *terminal_standout_end;    /* {tc_char,"se" */
  3413.  
  3414. /* Reverse Video */
  3415. char *terminal_inverse_begin;    /* {tc_char,"mr" */
  3416. char *terminal_end_attributes;    /* {tc_char,"me" */
  3417.  
  3418. /* Ding! */
  3419.  
  3420. char *terminal_ear_bell;    /* {tc_char,"bl" */
  3421.  
  3422. /* Terminal Initialization */
  3423.  
  3424. char *terminal_use_begin;    /* {tc_char,"ti" */
  3425. char *terminal_use_end;        /* {tc_char,"te" */
  3426.  
  3427. /* Padding Stuff */
  3428.  
  3429. char *terminal_padding;        /* {tc_char,"pc" */
  3430.  
  3431. /* Now the whopping big array */
  3432.  
  3433. typedef struct {
  3434.   char type;
  3435.   char *name;
  3436.   char *value;
  3437. } termcap_capability_struct;
  3438.  
  3439. termcap_capability_struct capabilities[] = {
  3440.  
  3441. /* Capabilities */
  3442.   
  3443.   {tc_int, "co", (char *) &terminal_columns},
  3444.   {tc_int, "li", (char *) &terminal_rows},
  3445.   {tc_flag, "gn", (char *) &terminal_is_generic},
  3446.  
  3447. /* Cursor Motion */
  3448.  
  3449.   {tc_char, "cm", (char *) &terminal_goto},
  3450.   {tc_char, "ho", (char *) &terminal_home},
  3451.  
  3452.   {tc_char, "le", (char *) &terminal_cursor_left},
  3453.   {tc_char, "nd", (char *) &terminal_cursor_right},
  3454.   {tc_char, "up", (char *) &terminal_cursor_up},
  3455.   {tc_char, "do", (char *) &terminal_cursor_down},
  3456.  
  3457. /* Screen Clearing */
  3458.   
  3459.   {tc_char, "cl", (char *) &terminal_clearpage},
  3460.   {tc_char, "cd", (char *) &terminal_clearEOP},
  3461.   {tc_char, "ce", (char *) &terminal_clearEOL},
  3462.   
  3463. /* "Standout" */
  3464.   {tc_char, "so", (char *) &terminal_standout_begin},
  3465.   {tc_char, "se", (char *) &terminal_standout_end},
  3466.  
  3467. /* Reverse Video */
  3468.   {tc_char, "mr", (char *) &terminal_inverse_begin},
  3469.   {tc_char, "me", (char *) &terminal_end_attributes},
  3470.  
  3471. /* Ding! */
  3472.  
  3473.   {tc_char, "bl", (char *) &terminal_ear_bell},
  3474.   
  3475. /* Terminal Initialization */
  3476.  
  3477.   {tc_char, "ti", (char *) &terminal_use_begin},
  3478.   {tc_char, "te", (char *) &terminal_use_end},
  3479.  
  3480. /* Padding Stuff */
  3481.   
  3482.   {tc_char, "pc", (char *) &terminal_padding},
  3483.  
  3484. /* Terminate this array with a var of type tc_last */
  3485.   {tc_last, NULL, NULL}
  3486.  
  3487. };
  3488.  
  3489. int terminal_opened_p = 0;
  3490.  
  3491. open_terminal_io ()
  3492. {
  3493.   int error;
  3494.  
  3495.   if (terminal_opened_p)
  3496.     return (NO_ERROR);
  3497.  
  3498.   if ((error = get_terminal_info ()) != NO_ERROR)
  3499.     return (error);
  3500.  
  3501.   if ((error = get_terminal_vars (capabilities)) != NO_ERROR)
  3502.     return (error);
  3503.  
  3504.   /* Now, make sure we have the capabilites that we need. */
  3505.   if (terminal_is_generic)
  3506.     return (BAD_TERMINAL);
  3507.  
  3508.   terminal_opened_p++;
  3509.   return (NO_ERROR);
  3510. }
  3511.  
  3512. get_terminal_info ()
  3513. {
  3514.   char temp_string_buffer[256];
  3515.   int result;
  3516.  
  3517.   char *terminal_name = getenv ("TERM");
  3518.  
  3519.   if (terminal_name == NULL || *terminal_name == 0
  3520.       || (strcmp (terminal_name, "dialup") == 0))
  3521.     {
  3522.       terminal_name = temp_string_buffer;
  3523.       printf ("\nTerminal Type:");
  3524.       fflush (stdout);
  3525.       fgets (terminal_name, 256, stdin);
  3526.       if (!(*terminal_name))
  3527.     return (NO_TERMINAL_DESCRIPTOR);
  3528.     }
  3529.  
  3530. /* #define VERBOSE_GET_TERMINAL 1 */
  3531. #ifdef VERBOSE_GET_TERMINAL
  3532.  
  3533. #define buffer_limit 256
  3534. #define opsys_termcap_filename "/etc/termcap"
  3535.  
  3536.   /* We hack question mark if that is what the user typed.  All this means
  3537.      is we read /etc/termcap, and prettily print out the names of terminals
  3538.      that we find. */
  3539.  
  3540.   if (terminal_name[0] == '?' && !terminal_name[1])
  3541.     {
  3542.       FILE *termcap_file;
  3543.       if ((termcap_file = fopen (opsys_termcap_filename, "r")) != NULL)
  3544.     {
  3545.       int result;
  3546.       char line_buffer[buffer_limit];
  3547.       int terminal_count = 0;
  3548.  
  3549.       while ((readline_termcap (termcap_file, line_buffer)) != EOF)
  3550.         {
  3551.           char first_char = *line_buffer;
  3552.           if (first_char == '#' || first_char == ' '
  3553.           || first_char == '\t' || first_char == '\n')
  3554.         ;
  3555.           else
  3556.         {
  3557.           /* Print the names the pretty way. */
  3558.           printf ("\n%s", line_buffer);    /* liar */
  3559.           terminal_count++;
  3560.         }
  3561.         }
  3562.       fclose (termcap_file);
  3563.  
  3564.       if (terminal_count)
  3565.         printf ("\n%d terminals listed.\n", terminal_count);
  3566.       else
  3567.         printf ("\nNo terminals were listed.  Brian's mistake.\n");
  3568.     }
  3569.       else
  3570.     {
  3571.       fprintf (stderr,
  3572.            "\nNo such system file as %s!\nWe lose badly.\n",
  3573.            opsys_termcap_filename);
  3574.       return (NO_TERMINAL_DESCRIPTOR);
  3575.     }
  3576.       return (get_terminal_info ());
  3577.     }
  3578. #endif /* VERBOSE_GET_TERMINAL */
  3579.  
  3580.   result = tgetent (termcap_buffer, terminal_name);
  3581.  
  3582.   if (!result)
  3583.     return (NO_TERMINAL_DESCRIPTOR);
  3584.   else
  3585.     return (NO_ERROR);
  3586. }
  3587.  
  3588. #ifdef VERBOSE_GET_TERMINAL
  3589. readline_termcap (stream, buffer)
  3590.      FILE *stream;
  3591.      char *buffer;
  3592. {
  3593.   int c;
  3594.   int buffer_index = 0;
  3595.  
  3596.   while ((c = getc (stream)) != EOF && c != '\n')
  3597.     {
  3598.       if (buffer_index != buffer_limit - 1)
  3599.     buffer[buffer_index++] = c;
  3600.     }
  3601.  
  3602.   buffer[buffer_index] = 0;
  3603.  
  3604.   if (c == EOF)
  3605.     return ((buffer_index) ? 0 : EOF);
  3606.   else
  3607.     return (0);
  3608. }
  3609. #endif /* VERBOSE_GET_TERMINAL */
  3610.  
  3611. /* For each element of "from_array", read the corresponding variable's
  3612.    value into the right place. */
  3613. get_terminal_vars (from_array)
  3614.      termcap_capability_struct from_array[];
  3615. {
  3616.   int i;
  3617.   register termcap_capability_struct *item;
  3618.   char *buffer;
  3619.  
  3620. #if !defined (GNU_TERMCAP)
  3621.   buffer = (char *) xmalloc (sizeof (termcap_buffer) + 1);
  3622. # define buffer_space &buffer
  3623. #else
  3624. # define buffer_space 0
  3625. #endif
  3626.  
  3627.   for (i = 0; (item = &from_array[i]) && (item->type != tc_last); i++)
  3628.     {
  3629.       switch (item->type)
  3630.     {
  3631.     case tc_int:
  3632.       *((int *) (item->value)) = tgetnum (item->name);
  3633.       break;
  3634.  
  3635.     case tc_flag:
  3636.       *((int *) item->value) = tgetflag (item->name);
  3637.       break;
  3638.  
  3639.     case tc_char:
  3640.       *((char **) item->value) = tgetstr (item->name, buffer_space);
  3641.       break;
  3642.  
  3643.     default:
  3644.       FERROR ("Bad entry scanned in tc_struct[].\n \
  3645.            Ask bfox@ai.mit.edu to fix this someday.\n");
  3646.     }
  3647.     }
  3648.  
  3649.   PC = terminal_padding ? terminal_padding[0] : 0;
  3650.   BC = terminal_cursor_left;
  3651.   UP = terminal_cursor_up;
  3652.   return (NO_ERROR);
  3653. }
  3654.  
  3655. /* Return the number of rows this terminal has. */
  3656. int
  3657. get_terminal_rows ()
  3658. {
  3659.   int rows = 0;
  3660.  
  3661. #if defined (TIOCGWINSZ)
  3662.   {
  3663.     int tty;
  3664.     struct winsize size;
  3665.  
  3666.     tty = fileno (stdin);
  3667.  
  3668.     if (ioctl (tty, TIOCGWINSZ, &size) != -1)
  3669.       rows = size.ws_row;
  3670.   }
  3671. #endif /* TIOCGWINSZ */
  3672.  
  3673.   if (!rows)
  3674.     rows = tgetnum ("li");
  3675.  
  3676.   if (rows <= 0)
  3677.     rows = 24;
  3678.  
  3679.   return (rows);
  3680. }
  3681.  
  3682. /* Return the number of columns this terminal has. */
  3683. get_terminal_columns ()
  3684. {
  3685.   int columns = 0;
  3686.  
  3687. #if defined (TIOCGWINSZ)
  3688.   {
  3689.     int tty;
  3690.     struct winsize size;
  3691.  
  3692.     tty = fileno (stdin);
  3693.  
  3694.     if (ioctl (tty, TIOCGWINSZ, &size) != -1)
  3695.       columns = size.ws_col;
  3696.   }
  3697. #endif /* TIOCGWINSZ */
  3698.  
  3699.   if (!columns)
  3700.     columns = tgetnum ("co");
  3701.  
  3702.   if (columns <= 0)
  3703.     columns = 80;
  3704.  
  3705.   return (columns);
  3706. }
  3707.  
  3708. /* #define TERMINAL_INFO_PRINTING */
  3709. #ifdef TERMINAL_INFO_PRINTING
  3710.  
  3711. /* Scan this (already "get_terminal_vars"ed) array, printing out the
  3712.    capability name, and value for each entry.  Pretty print the value
  3713.    so that the terminal doesn't actually do anything, shitbrain. */
  3714. show_terminal_info (from_array)
  3715.      termcap_capability_struct from_array[];
  3716. {
  3717.   register int i;
  3718.   register termcap_capability_struct *item;
  3719.  
  3720.   for (i = 0; ((item = &from_array[i]) && ((item->type) != tc_last)); i++)
  3721.     {
  3722.  
  3723.       char *type_name;
  3724.       switch (item->type)
  3725.     {
  3726.     case tc_int:
  3727.       type_name = "int ";
  3728.       break;
  3729.     case tc_flag:
  3730.       type_name = "flag";
  3731.       break;
  3732.     case tc_char:
  3733.       type_name = "char";
  3734.       break;
  3735.     default:
  3736.       type_name = "Broken";
  3737.     }
  3738.  
  3739.       printf ("\t%s\t%s = ", type_name, item->name);
  3740.  
  3741.       switch (item->type)
  3742.     {
  3743.     case tc_int:
  3744.     case tc_flag:
  3745.       printf ("%d", *((int *) item->value));
  3746.       break;
  3747.     case tc_char:
  3748.       tc_pretty_print (*((char **) item->value));
  3749.       break;
  3750.     }
  3751.       printf ("\n");
  3752.     }
  3753. }
  3754.  
  3755. /* Print the contents of string without sending anything that isn't
  3756.    a normal printing ASCII character. */
  3757. tc_pretty_print (string)
  3758.      register char *string;
  3759. {
  3760.   register char c;
  3761.  
  3762.   while (c = *string++)
  3763.     {
  3764.       if (CTRLP (c))
  3765.     {
  3766.       putchar ('^');
  3767.       c += 64;
  3768.     }
  3769.       putchar (c);
  3770.     }
  3771. }
  3772. #endif TERMINAL_INFO_PRINTING
  3773.  
  3774.  
  3775. /* **************************************************************** */
  3776. /*                                    */
  3777. /*            Character IO, and driver            */
  3778. /*                                    */
  3779. /* **************************************************************** */
  3780.  
  3781. char *widest_line;
  3782. int terminal_inited_p = 0;
  3783.  
  3784. /* Start up the character io stuff. */
  3785. init_terminal_io ()
  3786. {
  3787.   if (!terminal_inited_p)
  3788.     {
  3789.       opsys_init_terminal ();
  3790.       terminal_rows = get_terminal_rows ();
  3791.       terminal_columns = get_terminal_columns ();
  3792.  
  3793.       widest_line = (char *) xmalloc (terminal_columns);
  3794.  
  3795.       terminal_inited_p = 1;
  3796.     }
  3797.  
  3798.   terminal_window.left = 0;
  3799.   terminal_window.top = 0;
  3800.   terminal_window.right = terminal_columns;
  3801.   terminal_window.bottom = terminal_rows;
  3802.  
  3803.   set_window (&terminal_window);
  3804.  
  3805.   terminal_window.bottom -= 2;
  3806.  
  3807.   set_window (&terminal_window);
  3808.  
  3809.   init_echo_area (the_window.left, the_window.bottom + 1,
  3810.           the_window.right, terminal_rows);
  3811.  
  3812.   /* Here is a list of things that the terminal has to be able to do. Do
  3813.      you think that this is too harsh? */
  3814.   if (!terminal_goto ||        /* We can't move the cursor. */
  3815.       !terminal_rows)        /* We don't how many lines it has. */
  3816.     {
  3817.       fprintf (stderr,
  3818.            "Your terminal is not clever enough to run info. Sorry.\n");
  3819.        exit (1);
  3820.     }
  3821. }
  3822.  
  3823. /* Ring the terminal bell. */
  3824. ding ()
  3825. {
  3826.   extern char *terminal_ear_bell;
  3827.  
  3828.   if (terminal_ear_bell)
  3829.     do_term (terminal_ear_bell);
  3830.   else
  3831.     putchar (CTRL ('G'));
  3832.  
  3833.   fflush (stdout);
  3834. }
  3835.  
  3836. int untyi_char = 0;
  3837. int inhibit_output = 0;
  3838.  
  3839. /* Return a character from stdin, or the last unread character
  3840.    if there is one available. */
  3841. blink_cursor ()
  3842. {
  3843.   int character;
  3844.  
  3845.   fflush (stdout);
  3846.   if (untyi_char)
  3847.     {
  3848.       character = untyi_char;
  3849.       untyi_char = 0;
  3850.     }
  3851.   else
  3852.     do { character = getc (stdin); } while (character == -1 && errno == EINTR);
  3853.  
  3854.   return (character);
  3855. }
  3856.  
  3857. /* Display single character on the terminal screen.  If the
  3858.    character would run off the right hand edge of the screen,
  3859.    advance the cursor to the next line. */
  3860. charout (character)
  3861.      int character;
  3862. {
  3863.   if (inhibit_output)
  3864.     return;
  3865.  
  3866.   /* This character may need special treatment if it is
  3867.      a control character. */
  3868.   if (CTRL_P (character))
  3869.     {
  3870.       switch (character)
  3871.     {
  3872.     case NEWLINE:
  3873.     case RETURN:
  3874.       print_cr ();
  3875.       break;
  3876.  
  3877.     case TAB:
  3878.       print_tab ();
  3879.       break;
  3880.  
  3881.     default:
  3882.       charout ('^');
  3883.       charout (UNCTRL (character));
  3884.     }
  3885.     }
  3886.   else
  3887.     {
  3888.       putchar (character);
  3889.       advance (1);
  3890.     }
  3891. }
  3892.  
  3893. /* Move the cursor AMOUNT character positions. */
  3894. advance (amount)
  3895.      int amount;
  3896. {
  3897.   int old_window_cv = the_window.cv;
  3898.  
  3899.   while (amount-- > 0)
  3900.     {
  3901.       the_window.ch++;
  3902.       if (the_window.ch >= the_window.right)
  3903.     {
  3904.       the_window.ch = (the_window.ch - the_window.right) + the_window.left;
  3905.       the_window.cv++;
  3906.  
  3907.       if (the_window.cv >= the_window.bottom)
  3908.         the_window.cv = the_window.top;
  3909.     }
  3910.     }
  3911.  
  3912.   if (the_window.cv != old_window_cv)
  3913.     goto_xy (the_window.ch, the_window.cv);
  3914. }
  3915.  
  3916. /* Print STRING and args using charout */
  3917. print_string (string, a1, a2, a3, a4, a5)
  3918.      char *string;
  3919. {
  3920.   int character;
  3921.   char buffer[2048];
  3922.   int idx = 0;
  3923.  
  3924.   sprintf (buffer, string, a1, a2, a3, a4, a5);
  3925.  
  3926.   while (character = buffer[idx++])
  3927.     charout (character);
  3928.  
  3929.   fflush (stdout);
  3930. }
  3931.  
  3932. /* Display a carriage return.
  3933.    Clears to the end of the line first. */
  3934. print_cr ()
  3935. {
  3936.   extern int typing_out;
  3937.   clear_eol ();
  3938.  
  3939.   if (typing_out)
  3940.     {                /* Do the "MORE" stuff. */
  3941.       int response;
  3942.  
  3943.       if (the_window.cv + 2 == the_window.bottom)
  3944.     {
  3945.       goto_xy (the_window.left, the_window.cv + 1);
  3946.       clear_eol ();
  3947.       print_string ("[More]");
  3948.       response = blink_cursor ();
  3949.       if (response != SPACE)
  3950.         {
  3951.           untyi_char = response;
  3952.           inhibit_output = 1;
  3953.           return;
  3954.         }
  3955.       else
  3956.         {
  3957.           goto_xy (the_window.left, the_window.cv);
  3958.           clear_eol ();
  3959.           goto_xy (the_window.left, the_window.top);
  3960.           return;
  3961.         }
  3962.     }
  3963.     }
  3964.   advance (the_window.right - the_window.ch);
  3965. }
  3966.  
  3967. /* Move the cursor to the next tab stop, blanking the intervening
  3968.    spaces along the way. */
  3969. print_tab ()
  3970. {
  3971.   int hpos, width, destination;
  3972.  
  3973.   hpos = the_window.ch - the_window.left;
  3974.   width = ((hpos + 8) & 0xf8) - hpos;
  3975.  
  3976.   destination = hpos + width + the_window.left;
  3977.  
  3978.   if (destination >= the_window.right)
  3979.     destination -= the_window.right;
  3980.  
  3981.   while (the_window.ch != destination)
  3982.     charout (SPACE);
  3983. }
  3984.  
  3985. display_width (character, hpos)
  3986.      int character, hpos;
  3987. {
  3988.   int width = 1;
  3989.  
  3990.   if (CTRL_P (character))
  3991.     {
  3992.       switch (character)
  3993.     {
  3994.     case RETURN:
  3995.     case NEWLINE:
  3996.       width = the_window.right - hpos;
  3997.       break;
  3998.     case TAB:
  3999.       width = ((hpos + 8) & 0xf8) - hpos;
  4000.       break;
  4001.     default:
  4002.       width = 2;
  4003.     }
  4004.     }
  4005.   return (width);
  4006. }
  4007.  
  4008. /* Like GOTO_XY, but do it right away. */
  4009. I_goto_xy (xpos, ypos)
  4010.      int xpos, ypos;
  4011. {
  4012.   goto_xy (xpos, ypos);
  4013.   fflush (stdout);
  4014. }
  4015.  
  4016. /* Move the cursor, (and cursor variables) to xpos, ypos. */
  4017. goto_xy (xpos, ypos)
  4018.      int xpos, ypos;
  4019. {
  4020.   the_window.ch = xpos;
  4021.   the_window.cv = ypos;
  4022.   opsys_goto_pos (xpos, ypos);
  4023. }
  4024.  
  4025. /* Clear the screen, leaving ch and cv at the top of the window. */
  4026. clear_screen ()
  4027. {
  4028.   goto_xy (the_window.left, the_window.top);
  4029.   clear_eop_slowly ();
  4030. }
  4031.  
  4032. clear_eop_slowly ()
  4033. {
  4034.   int temp_ch = the_window.ch;
  4035.   int temp_cv = the_window.cv;
  4036.  
  4037.   clear_eol ();
  4038.  
  4039.   while (++the_window.cv < the_window.bottom)
  4040.     {
  4041.       goto_xy (the_window.left, the_window.cv);
  4042.       clear_eol ();
  4043.     }
  4044.   goto_xy (temp_ch, temp_cv);
  4045. }
  4046.  
  4047. /* Clear from current cursor position to end of page. */
  4048. clear_eop ()
  4049. {
  4050.   if (terminal_clearEOP)
  4051.     do_term (terminal_clearEOP);
  4052.   else
  4053.     clear_eop_slowly ();
  4054. }
  4055.  
  4056. /* Clear from current cursor position to end of screen line */
  4057. clear_eol ()
  4058. {
  4059.   int temp_ch = the_window.ch;
  4060.  
  4061.   if (terminal_clearEOL)
  4062.     do_term (terminal_clearEOL);
  4063.   else
  4064.     {
  4065.       char *line = widest_line;
  4066.       int i;
  4067.  
  4068.       for (i = 0; i < the_window.right - the_window.ch; i++)
  4069.     line[i] = ' ';
  4070.       line[i] = '\0';
  4071.  
  4072.       printf ("%s", line);
  4073.     }
  4074.   goto_xy (temp_ch, the_window.cv);
  4075. }
  4076.  
  4077. /* Call FUNCTION with WINDOW active.  You can pass upto 5 args to the
  4078.    function.  This returns whatever FUNCTION returns. */
  4079. int
  4080. with_output_to_window (window, function, arg1, arg2, arg3, arg4, arg5)
  4081.      WINDOW *window;
  4082.      Function *function;
  4083. {
  4084.   int result;
  4085.  
  4086.   push_window ();
  4087.   set_window (window);
  4088.   result = (*function) (arg1, arg2, arg3, arg4, arg5);
  4089.   pop_window ();
  4090.   return (result);
  4091. }
  4092.  
  4093. /* Given a pointer to a window data structure, make that
  4094.    the current window. */
  4095. set_window (window)
  4096.      WINDOW *window;
  4097. {
  4098.   bcopy (window, &the_window, sizeof (WINDOW));
  4099. }
  4100.  
  4101. /* Save the current window on the window stack. */
  4102. push_window ()
  4103. {
  4104.   WINDOW_LIST *new_window = (WINDOW_LIST *) xmalloc (sizeof (WINDOW_LIST));
  4105.  
  4106.   new_window->next_window = window_stack;
  4107.   window_stack = new_window;
  4108.   new_window->ch = the_window.ch;
  4109.   new_window->cv = the_window.cv;
  4110.   new_window->top = the_window.top;
  4111.   new_window->bottom = the_window.bottom;
  4112.   new_window->left = the_window.left;
  4113.   new_window->right = the_window.right;
  4114. }
  4115.  
  4116. /* Pop the top of the window_stack into the_window. */
  4117. pop_window ()
  4118. {
  4119.   set_window ((WINDOW *)window_stack);
  4120.  
  4121.   if (window_stack->next_window)
  4122.     {
  4123.       WINDOW_LIST *thing_to_free = window_stack;
  4124.       window_stack = window_stack->next_window;
  4125.       free (thing_to_free);
  4126.     }
  4127.  
  4128.   goto_xy (the_window.ch, the_window.cv);
  4129. }
  4130.  
  4131. /* **************************************************************** */
  4132. /*                                    */
  4133. /*            "Opsys" functions.                */
  4134. /*                                    */
  4135. /* **************************************************************** */
  4136.  
  4137. /* The lowlevel terminal/file interface.  Nothing ever really gets
  4138.    low level when you're writing in C, though.
  4139.  
  4140.    This file contains all of the "opsys" labels.  You have to make
  4141.    a different one if you want GNU Info to run on machines that don't
  4142.    have Unix.  */
  4143.  
  4144. extern char *terminal_use_begin, *terminal_use_end, *terminal_goto;
  4145.  
  4146. #if defined (TIOCGETC)
  4147. struct tchars original_tchars;
  4148. #endif
  4149.  
  4150. #if defined (TIOCGLTC)
  4151. struct ltchars original_ltchars;
  4152. #endif
  4153.  
  4154. #if defined (USG)
  4155. struct termio original_termio, ttybuff;
  4156. #else
  4157. int original_tty_flags = 0;
  4158. int original_lmode;
  4159. struct sgttyb ttybuff;
  4160. #endif /* !USG */
  4161.  
  4162. /* Yes, that's right, do things that the machine needs to get
  4163.    the terminal into a usable mode. */
  4164. opsys_init_terminal ()
  4165. {
  4166.   int tty = fileno (stdin);
  4167.  
  4168. #if defined (USG)
  4169.   ioctl (tty, TCGETA, &original_termio);
  4170.   ioctl (tty, TCGETA, &ttybuff);
  4171.   ttybuff.c_iflag &= (~ISTRIP & ~INLCR & ~IGNCR & ~ICRNL &~IXON);
  4172.   ttybuff.c_oflag &= (~ONLCR & ~OCRNL);
  4173.   ttybuff.c_lflag &= (~ICANON & ~ECHO);
  4174.  
  4175.   ttybuff.c_cc[VMIN] = 1;
  4176.   ttybuff.c_cc[VTIME] = 0;
  4177.  
  4178.   if (ttybuff.c_cc[VINTR] = DELETE)
  4179.     ttybuff.c_cc[VINTR] = -1;
  4180.  
  4181.   if (ttybuff.c_cc[VQUIT] = DELETE)
  4182.     ttybuff.c_cc[VQUIT] = -1;
  4183.  
  4184.   ioctl (tty, TCSETA, &ttybuff);
  4185. #else /* !USG */
  4186.  
  4187.   ioctl (tty, TIOCGETP, &ttybuff);
  4188.  
  4189.   if (!original_tty_flags)
  4190.     original_tty_flags = ttybuff.sg_flags;
  4191.  
  4192.   /* Make this terminal pass 8 bits around while we are using it. */
  4193. #ifdef PASS8
  4194.   ttybuff.sg_flags |= PASS8;
  4195. #endif
  4196.  
  4197. #if defined (TIOCLGET) && defined (LPASS8)
  4198.   {
  4199.     int flags;
  4200.     ioctl (tty, TIOCLGET, &flags);
  4201.     original_lmode = flags;
  4202.     flags |= LPASS8;
  4203.     ioctl (tty, TIOCLSET, &flags);
  4204.   }
  4205. #endif
  4206.  
  4207. #ifdef TIOCGETC
  4208.   {
  4209.     struct tchars temp;
  4210.  
  4211.     ioctl (tty, TIOCGETC, &original_tchars);
  4212.     bcopy (&original_tchars, &temp, sizeof (struct tchars));
  4213.  
  4214.     temp.t_startc = temp.t_stopc = -1;
  4215.  
  4216.     /* If the quit character conflicts with one of our commands, then
  4217.        make it go away. */
  4218.     if (temp.t_intrc == DELETE)
  4219.       temp.t_intrc == -1;
  4220.  
  4221.     if (temp.t_quitc == DELETE)
  4222.       temp.t_quitc == -1;
  4223.  
  4224.     ioctl (tty, TIOCSETC, &temp);
  4225.   }
  4226. #endif /* TIOCGETC */
  4227.  
  4228. #ifdef TIOCGLTC
  4229.   {
  4230.     struct ltchars temp;
  4231.  
  4232.     ioctl (tty, TIOCGLTC, &original_ltchars);
  4233.     bcopy (&original_ltchars, &temp, sizeof (struct ltchars));
  4234.  
  4235.     /* Make the interrupt keys go away.  Just enough to make people happy. */
  4236.     temp.t_lnextc = -1;        /* C-v */
  4237.  
  4238.     ioctl (tty, TIOCSLTC, &temp);
  4239.   }
  4240. #endif /* TIOCGLTC */
  4241.  
  4242.   ttybuff.sg_flags &= ~ECHO;
  4243.   ttybuff.sg_flags |= CBREAK;
  4244.   ioctl (tty, TIOCSETN, &ttybuff);
  4245. #endif /* !USG */
  4246.  
  4247.   open_terminal_io ();
  4248.   do_term (terminal_use_begin);
  4249. }
  4250.  
  4251. /* Fix the terminal that I broke. */
  4252. restore_io ()
  4253. {
  4254.   int tty = fileno (stdin);
  4255.  
  4256. #if defined (USG)
  4257.   ioctl (tty, TCSETA, &original_termio);
  4258. #else
  4259.   ioctl (tty, TIOCGETP, &ttybuff);
  4260.   ttybuff.sg_flags = original_tty_flags;
  4261.   ioctl (tty, TIOCSETN, &ttybuff);
  4262.  
  4263. #ifdef TIOCGETC
  4264.   ioctl (tty, TIOCSETC, &original_tchars);
  4265. #endif /* TIOCGETC */
  4266.  
  4267. #ifdef TIOCGLTC
  4268.   ioctl (tty, TIOCSLTC, &original_ltchars);
  4269. #endif /* TIOCGLTC */
  4270.  
  4271. #if defined (TIOCLGET) && defined (LPASS8)
  4272.   ioctl (tty, TIOCLSET, &original_lmode);
  4273. #endif
  4274.  
  4275. #endif /* !USG */
  4276.   do_term (terminal_use_end);
  4277. }
  4278.  
  4279. opsys_goto_pos (xpos, ypos)
  4280.      int xpos, ypos;
  4281. {
  4282.   do_term (tgoto (terminal_goto, xpos, ypos));
  4283. }
  4284.  
  4285. character_output_function (character)
  4286.      char character;
  4287. {
  4288.   putchar (character);
  4289. }
  4290.  
  4291. /* Generic interface to tputs. */
  4292. do_term (command)
  4293.      char *command;
  4294. {
  4295.   /* Send command to the terminal, with appropriate padding. */
  4296.   if (command)
  4297.     tputs (command, 1, character_output_function);
  4298. }
  4299.  
  4300. /* Filename manipulators, and the like. */
  4301. char local_temp_filename[FILENAME_LEN];
  4302.  
  4303. char *info_suffixes[] = {
  4304.   "",
  4305.   ".info",
  4306.   "-info",
  4307.   (char *)NULL
  4308. };
  4309.  
  4310. /* A structure which associates the argument passed into a function with
  4311.    the result from that function. */
  4312. typedef struct {
  4313.   char *called;
  4314.   char *result;
  4315. } CALLED_RESULTS;
  4316.  
  4317. /* An array of remembered arguments and results. */
  4318. static CALLED_RESULTS **opsys_callers_and_results = (CALLED_RESULTS **)NULL;
  4319. static int next_caller_and_result = 0;
  4320. static int callers_and_results_size = 0;
  4321.  
  4322. /* Find the result for having already called opsys_filename () with CALLER. */
  4323. char *
  4324. find_opsys_filename_result (caller)
  4325.      char *caller;
  4326. {
  4327.   if (caller && opsys_callers_and_results)
  4328.     {
  4329.       register int i;
  4330.       for (i = 0; opsys_callers_and_results[i]; i++)
  4331.     {
  4332.       if (strcmp (opsys_callers_and_results[i]->called, caller) == 0)
  4333.         return (opsys_callers_and_results[i]->result);
  4334.     }
  4335.     }
  4336.   return (char *)NULL;;
  4337. }
  4338.   
  4339. /* Add an argument and result to our list. */
  4340. void
  4341. add_caller_and_result (caller, result)
  4342.      char *caller, *result;
  4343. {
  4344.   CALLED_RESULTS *new;
  4345.  
  4346.   if (next_caller_and_result + 2 > callers_and_results_size)
  4347.     {
  4348.       int alloc_size;
  4349.       callers_and_results_size += 10;
  4350.  
  4351.       alloc_size = callers_and_results_size * sizeof (CALLED_RESULTS *);
  4352.  
  4353.       opsys_callers_and_results = (CALLED_RESULTS **)
  4354.     xrealloc (opsys_callers_and_results, alloc_size);
  4355.     }
  4356.  
  4357.   new = (CALLED_RESULTS *)xmalloc (sizeof (CALLED_RESULTS));
  4358.   new->called = savestring (caller);
  4359.   new->result = result ? savestring (result) : (char *)NULL;
  4360.  
  4361.   opsys_callers_and_results[next_caller_and_result++] = new;
  4362.   opsys_callers_and_results[next_caller_and_result] = (CALLED_RESULTS *)NULL;
  4363. }
  4364.  
  4365. /* Expand the filename in partial to make a real name for
  4366.    this operating system.  This looks in INFO_PATHS in order to
  4367.    find the correct file.  If it can't find the file, it just
  4368.    returns the path as you gave it. */
  4369. char *
  4370. opsys_filename (partial)
  4371.      char *partial;
  4372. {
  4373.   int initial_character;
  4374.   char *my_infopath;
  4375.  
  4376.   if (partial && (initial_character = *partial))
  4377.     {
  4378.       char *possible_result;
  4379.  
  4380.       if (initial_character == '/')
  4381.     return (partial);
  4382.  
  4383.       possible_result = find_opsys_filename_result (partial);
  4384.       if (possible_result)
  4385.     return (possible_result);
  4386.  
  4387.       if (initial_character == '~')
  4388.     {
  4389.       if (partial[1] == '/')
  4390.         {
  4391.           /* Return the concatenation of HOME and the rest
  4392.          of the string. */
  4393.           strcpy (local_temp_filename, getenv ("HOME"));
  4394.           strcat (local_temp_filename, &partial[2]);
  4395.           return (local_temp_filename);
  4396.         }
  4397.       else
  4398.         {
  4399.           struct passwd *user_entry;
  4400.           int i, c;
  4401.           char username[257];
  4402.  
  4403.           for (i = 1; c = partial[i]; i++)
  4404.         {
  4405.           if (c == '/')
  4406.             break;
  4407.           else
  4408.             username[i - 1] = c;
  4409.         }
  4410.           username[i - 1] = '\0';
  4411.  
  4412.           if (!(user_entry = getpwnam (username)))
  4413.         {
  4414.           display_error ("Not a registered user!");
  4415.           return (partial);
  4416.         }
  4417.           strcpy (local_temp_filename, user_entry->pw_dir);
  4418.           strcat (local_temp_filename, &partial[i]);
  4419.           return (local_temp_filename);
  4420.         }
  4421.     }
  4422.  
  4423.       if (initial_character == '.' &&
  4424.       (partial[1] == '/' || (partial[1] == '.' && partial[2] == '/')))
  4425.     {
  4426. #if defined (USG)
  4427.       if (!getcwd (local_temp_filename, FILENAME_LEN))
  4428. #else
  4429.       if (!getwd (local_temp_filename))
  4430. #endif
  4431.         {
  4432.           display_error (local_temp_filename);
  4433.           return (partial);
  4434.         }
  4435.  
  4436.       strcat (local_temp_filename, "/");
  4437.       strcat (local_temp_filename, partial);
  4438.       return (local_temp_filename);
  4439.     }
  4440.  
  4441.       /* If the current info file has a directory, then search that directory
  4442.      first. */
  4443.       {
  4444.     register char *ptr, *file;
  4445.  
  4446.     file = find_opsys_filename_result (current_info_file);
  4447.  
  4448.     if (file && (ptr = (char *)rindex (file, '/')) != (char *)NULL)
  4449.       {
  4450.         register int len = (ptr - file);
  4451.  
  4452.         my_infopath = (char *)
  4453.           xmalloc (2 + strlen (file) + strlen (infopath));
  4454.  
  4455.         strncpy (my_infopath, file, len);
  4456.         sprintf (my_infopath + len, ":%s", infopath);
  4457.       }
  4458.     else
  4459.       my_infopath = savestring (infopath);
  4460.       }
  4461.  
  4462.       if (opsys_info_file_in_path (partial, my_infopath, local_temp_filename))
  4463.     {
  4464.       free (my_infopath);
  4465.       add_caller_and_result (partial, local_temp_filename);
  4466.       return (local_temp_filename);
  4467.     }
  4468.  
  4469.       free (my_infopath);
  4470.     }
  4471.   return (partial);
  4472. }
  4473.  
  4474. #if !defined (S_ISREG) && defined (S_IFREG)
  4475. #  define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
  4476. #endif /* !S_ISREG && S_IFREG */
  4477.  
  4478. #if !defined (S_ISDIR) && defined (S_IFDIR)
  4479. #  define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
  4480. #endif /* !S_ISDIR && S_IFDIR */
  4481.  
  4482. /* Scan the list of directories in PATH looking for FILENAME.
  4483.    If we find one that is a regular file, stuff the answer into
  4484.    RETURN_STRING.  Return non-zero if a file was found, zero otherwise. */
  4485. int
  4486. opsys_info_file_in_path (filename, path, return_string)
  4487.      char *filename, *path;
  4488.      char *return_string;
  4489. {
  4490.   struct stat finfo;
  4491.   char *temp_dirname, *extract_colon_unit ();
  4492.   char *test_file = return_string;
  4493.   int statable, dirname_index = 0;
  4494.  
  4495.   while (temp_dirname = extract_colon_unit (path, &dirname_index))
  4496.     {
  4497.       register int i, pre_suffix_length;
  4498.  
  4499.       strcpy (test_file, temp_dirname);
  4500.       if (temp_dirname[(strlen (temp_dirname)) - 1] != '/')
  4501.     strcat (test_file, "/");
  4502.       strcat (test_file, filename);
  4503.  
  4504.       pre_suffix_length = strlen (test_file);
  4505.  
  4506.       free (temp_dirname);
  4507.  
  4508.       for (i = 0; info_suffixes[i]; i++)
  4509.     {
  4510.       strcpy (test_file + pre_suffix_length, info_suffixes[i]);
  4511.  
  4512.       statable = (stat (test_file, &finfo) == 0);
  4513.  
  4514.       /* If we have found a regular file, then use that.  Else, if we
  4515.          have found a directory, look in that directory for this file. */
  4516.       if (statable)
  4517.         {
  4518.           if (S_ISREG (finfo.st_mode))
  4519.         return (1);
  4520.           else if (S_ISDIR (finfo.st_mode))
  4521.         {
  4522.           char *newpath = savestring (test_file);
  4523.           char *filename_only = (char *)rindex (filename, '/');
  4524.  
  4525.           if (opsys_info_file_in_path
  4526.               (filename_only ? filename_only : filename,
  4527.                newpath, return_string))
  4528.             {
  4529.               free (newpath);
  4530.               return (1);
  4531.             }
  4532.         }
  4533.         }
  4534.     }
  4535.     }
  4536.   return (0);
  4537. }
  4538.  
  4539. /* Given a string containing units of information separated by colons,
  4540.    return the next one pointed to by IDX, or NULL if there are no more.
  4541.    Advance IDX to the character after the colon. */
  4542. char *
  4543. extract_colon_unit (string, idx)
  4544.      char *string;
  4545.      int *idx;
  4546. {
  4547.   register int i, start;
  4548.  
  4549.   i = start = *idx;
  4550.   if ((i >= strlen (string)) || !string)
  4551.     return ((char *) NULL);
  4552.  
  4553.   while (string[i] && string[i] != ':')
  4554.     i++;
  4555.   if (i == start)
  4556.     {
  4557.       return ((char *) NULL);
  4558.     }
  4559.   else
  4560.     {
  4561.       char *value = (char *) xmalloc (1 + (i - start));
  4562.       strncpy (value, &string[start], (i - start));
  4563.       value[i - start] = '\0';
  4564.       if (string[i])
  4565.     ++i;
  4566.       *idx = i;
  4567.       return (value);
  4568.     }
  4569. }
  4570.  
  4571. /* **************************************************************** */
  4572. /*                                    */
  4573. /*            The echo area.                    */
  4574. /*                                    */
  4575. /* **************************************************************** */
  4576.  
  4577. WINDOW echo_area = {0, 0, 0, 0, 0, 0};
  4578. int echo_area_open_p = 0;
  4579. char modeline[256];
  4580. WINDOW modeline_window = {0, 0, 0, 0, 0, 0};
  4581.  
  4582. /* Define the location of the echo area. Also inits the
  4583.    modeline as well. */
  4584. init_echo_area (left, top, right, bottom)
  4585.      int left, top, right, bottom;
  4586. {
  4587.   echo_area.left = modeline_window.left = left;
  4588.   echo_area.top = top;
  4589.   modeline_window.top = top - 1;
  4590.   echo_area.right = modeline_window.right = right;
  4591.   echo_area.bottom = bottom;
  4592.   modeline_window.bottom = modeline_window.top;
  4593. }
  4594.  
  4595. /* Make the echo_area_window be the current window, and only allow
  4596.    output in there.  Clear the window to start. */
  4597. new_echo_area ()
  4598. {
  4599.   if (!echo_area_open_p)
  4600.     {
  4601.       push_window ();
  4602.       set_window (&echo_area);
  4603.       echo_area_open_p = 1;
  4604.     }
  4605.   goto_xy (the_window.left, the_window.top);
  4606.   clear_eop ();
  4607. }
  4608.  
  4609. /* Return output to the previous window. */
  4610. close_echo_area ()
  4611. {
  4612.   if (!echo_area_open_p)
  4613.     return;
  4614.  
  4615.   pop_window ();
  4616.   echo_area_open_p = 0;
  4617. }
  4618.  
  4619. /* Clear the contents of the echo area. */
  4620. clear_echo_area ()
  4621. {
  4622.   new_echo_area ();
  4623.   close_echo_area ();
  4624. }
  4625.  
  4626. /* Create and display the modeline. */
  4627. make_modeline ()
  4628. {
  4629.   int width = modeline_window.right - modeline_window.left;
  4630.   char textual_position[6];
  4631.  
  4632.   if (pagetop == nodetop)
  4633.     {
  4634.       if (pagebot == nodebot)
  4635.     sprintf (textual_position, "All");
  4636.       else
  4637.     sprintf (textual_position, "Top");
  4638.     }
  4639.   else
  4640.     {
  4641.       if (pagebot == nodebot)
  4642.     sprintf (textual_position, "Bot");
  4643.       else
  4644.     sprintf (textual_position, "%2d%%",
  4645.          100 * (pagetop - nodetop) / (nodebot - nodetop));
  4646.     }
  4647.     
  4648.   sprintf (modeline, "Info: (%s)%s, %d lines ---%s",
  4649.        current_info_file, current_info_node, nodelines, textual_position);
  4650.  
  4651.   if (strnicmp
  4652.       (opsys_filename (current_info_file), last_loaded_info_file,
  4653.        strlen (last_loaded_info_file)) != 0)
  4654.     {
  4655.       sprintf (&modeline[strlen (modeline)], ", Subfile: %s",
  4656.            last_loaded_info_file);
  4657.     }
  4658.  
  4659.   if (strlen (modeline) < width)
  4660.     {
  4661.       int idx = strlen (modeline);
  4662.       while (idx != width)
  4663.     modeline[idx++] = '-';
  4664.       modeline[idx] = '\0';
  4665.     }
  4666.  
  4667.   if (strlen (modeline) > width)
  4668.     modeline[width] = '\0';
  4669.   push_window ();
  4670.   set_window (&modeline_window);
  4671.   goto_xy (the_window.left, the_window.top);
  4672.  
  4673.   if (terminal_inverse_begin)
  4674.     do_term (terminal_inverse_begin);
  4675.   print_string ("%s", modeline);
  4676.   if (terminal_inverse_begin)
  4677.     do_term (terminal_end_attributes);
  4678.  
  4679.   pop_window ();
  4680. }
  4681.  
  4682. int typing_out = 0;
  4683.  
  4684. /* Prepare to do output to the typeout window.  If the
  4685.    typeout window is already open, ignore this clown. */
  4686. open_typeout ()
  4687. {
  4688.   if (typing_out)
  4689.     return;
  4690.  
  4691.   push_window ();
  4692.   set_window (&terminal_window);
  4693.   goto_xy (the_window.ch, the_window.cv);
  4694.   typing_out = window_bashed = 1;
  4695. }
  4696.  
  4697. /* Close the currently open typeout window. */
  4698. close_typeout ()
  4699. {
  4700.   if (inhibit_output)
  4701.     inhibit_output = 0;
  4702.   else
  4703.     {
  4704.       do { untyi_char = getc (stdin); }
  4705.         while (untyi_char == -1 && errno == EINTR);
  4706.  
  4707.       if (untyi_char == SPACE)
  4708.     untyi_char = 0;
  4709.     }
  4710.   pop_window ();
  4711.   typing_out = 0;
  4712. }
  4713.  
  4714. void *
  4715. xrealloc (pointer, bytes)
  4716.      char *pointer;
  4717.      int bytes;
  4718. {
  4719.   void *temp;
  4720.  
  4721.   if (pointer == (char *)NULL)
  4722.     temp = (void *) xmalloc (bytes);
  4723.   else
  4724.     temp = (void *) realloc (pointer, bytes);
  4725.  
  4726.   if (temp == (void *)NULL)
  4727.     {
  4728.       fprintf (stderr, "Virtual memory exhausted\n");
  4729.       restore_io ();
  4730.       exit (2);
  4731.     }
  4732.   return (temp);
  4733. }
  4734.  
  4735. void *
  4736. xmalloc (bytes)
  4737.      int bytes;
  4738. {
  4739.   void *temp = (void *) malloc (bytes);
  4740.  
  4741.   if (temp == (void *) NULL)
  4742.     {
  4743.       fprintf (stderr, "Virtual memory exhausted\n");
  4744.       restore_io ();
  4745.       exit (2);
  4746.     }
  4747.   return (temp);
  4748. }
  4749.